# Kill the thread in a long computation

Hello

I'm trying to run a long factor() computation and kill it if it took more than 10 seconds. As a simple example to kill a thread in python using signals consider the following code:

def loop_forever():
import time
while True:
time.sleep(1)
return 1;

def handler(signum, frame):
raise Exception("end of time")

def timeout(func, args=(), kwargs={}, timeout_duration=10):
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout_duration)
try:
func(*args, **kwargs)
except Exception:
pass
signal.alarm(0)

timeout(loop_forever)


This would run fine and the thread would be killed after 10 seconds

If on the other hand instead of calling loop_forever() I call

def crackKey():
return factor(Primes().next(2^400) * Primes().next(2^500))

timeout(crackKey)


The thread is not killed after 10 seconds and it continues the computation Any idea why this is happening?

cheers

edit retag close merge delete

Sort by » oldest newest most voted

In my previous answer, i guessed that the problem comes from cython, that does not handle the Python signal correctly, as if it runs too fast to hear it ;) Here is a workaround: the timetout() function should operate at a lower level. We will launch the computation as a new process, and kill the process after timeout_duration. The kill signal is not sent at the Python level, but at the kernel level.

To compute a function in a separate process, Sage has a @fork decorator, here is how to use it in your framework:

sage: def timeout(func, args=(), kwargs={}, timeout_duration=10):
....:    @fork(timeout=timeout_duration, verbose=True)
....:    def my_new_func():
....:        return func(*args, **kwargs)
....:    return my_new_func()


Now you can see that both

sage: timeout(loop_forever)


and

sage: timeout(my_func)


stop after time_duration !

more

wow it works!!! brilliant. A noob like me didn't realize the existence of these decorations at all. many thanks

( 2013-05-11 07:31:20 +0200 )edit
1

Tiny remark: @fork creates another *process*, not another thread.

( 2013-05-11 20:20:15 +0200 )edit

You are right, i corrected this.

( 2013-05-13 17:02:43 +0200 )edit

The first reason why this does not work is that the expression factor(Primes().next(2^400)*Primes().next(2^500)) is an object of the class sage.structure.factorization_integer.IntegerFactorization, but not a function. Hence, if you send this expression to the timeout() function, it will be computed before being sent to the timeout() function, which won't kill the computation. Hence, you will experience the same problem as if you did:

sage: timeout(loop_forever())


Which will loop infinitely. So, you should do something like:

sage: def my_func():
....:     return factor(Primes().next(2^400) * Primes().next(2^500))
sage: timeout(my_func)


And, surprise, it doesn't work better ! Here is a possible reason. As you can check by typing:

sage: a = 123
sage: a.factor??


the factor() function is made of cython (see the use of cdef). So this may be the problem, let's investigate this track further. For that, open a notebook and create one cell with:

%cython
def loop_forever():
import time
while True:
time.sleep(1)
return 1;


(this will create the loop_forever() function as a cython one), and another cell with:

import signal

def handler(signum, frame):
raise Exception("end of time")

def timeout(func, args=(), kwargs={}, timeout_duration=10):
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout_duration)
try:
func(*args, **kwargs)
except Exception:
pass
signal.alarm(0)


Then try:

timeout(loop_forever)


As you can see, the computation does not stop either ! I guess cython is the reason why your problem happened.

more

Thanks for the investigation. I guess I prefer to avoid the notebook for now, since it adds a level of complexity while tracking the problem. As I mentioned loop forever can be killed in a sage session in the terminal in contrast to the factor() function. In reality the call to factor is never made directly from the timeout() but through another function that calls factor() and is itself passed to the timeout()

( 2013-05-10 17:43:36 +0200 )edit
1

I proposed to use the notebook to ease the use of the %cython markup (not available in the Sage command line), see [this page](http://www.sagemath.org/doc/developer/coding_in_cython.html#writing-cython-code-in-sage). You can reproduce the investigation completely from the Sage command line by doing the following: Create a file named /tmp/loop.spyx and add the following content to it: def loop_forever(): import time while True: time.sleep(1) return 1; Then in the Sage command line, do sage: %attach /tmp/loop.spyx Compiling /tmp/loop.spyx... and do your test. You will see that timeout(loop_forever) will not stop either !

( 2013-05-10 17:56:06 +0200 )edit

ahh I see. I updated the question to reflect a correct code

( 2013-05-11 06:25:00 +0200 )edit