func_timeout icon indicating copy to clipboard operation
func_timeout copied to clipboard

How to stop timeout task

Open fengyehong opened this issue 6 years ago • 7 comments

I am trying to figure out the correct way to use this library, I use the following test code:

Python 2.7.5 (default, Sep 15 2016, 22:37:39) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> from func_timeout import func_set_timeout
>>> 
>>> @func_set_timeout(3)
... def test():
...     while True:
...         time.sleep(1)
...         print "test"
... 
>>> 
>>> test()
test
test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/site-packages/func_timeout/dafunc.py", line 185, in <lambda>
    return wraps(func)(lambda *args, **kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs))
  File "/usr/lib/python2.7/site-packages/func_timeout/dafunc.py", line 101, in func_timeout
    raise FunctionTimedOut('', timeout, func, args, kwargs)
func_timeout.exceptions.FunctionTimedOut: Function test (args=()) (kwargs={}) timed out after 3.000000 seconds.

>>> test
test
test
test
test

KeyboardInterrupt
>>> test
test
test
test

After the func_timeout.exceptions.FunctionTimedOut, the test function is not terminated, how can i stop the function?

fengyehong avatar Nov 28 '19 09:11 fengyehong

you should try this code for same:

Code :

import time
from func_timeout import func_set_timeout , FunctionTimedOut
 
@func_set_timeout(3)
def test():
    while True:
        time.sleep(1)
        print("test")

try:
    test()
except FunctionTimedOut:
    print("end test!!")
print("function ended syccesfully!!!")

Output :


test
test
end test!!
function ended syccesfully!!!

Utsav13 avatar Nov 28 '19 09:11 Utsav13

@Utsav13 Thanks for relay, but the function test doesn't actually stopped, it's just because the main thread ended and the process exited. I can test it with this:

import time
from func_timeout import func_set_timeout , FunctionTimedOut

@func_set_timeout(3)
def test():
    while True:
        time.sleep(1)
        print("test")

try:
    test()
except FunctionTimedOut:
    print("end test!!")
print("function ended syccesfully!!!")

for i in range(1,10):
    time.sleep(1)

Output:

test
test
end test!!
function ended syccesfully!!!
test
test
test
test
test
test
test
test
test

fengyehong avatar Nov 28 '19 10:11 fengyehong

okay, previously I tested code with python 3.6 and it works fine. but when I have tested it with python 2.7 then I got the same error which u have right now. so if it is possible to use the same code with python 3.x then you have not to change anything with your code. but if your python version is mandatory for your use then you can use simple tricks to exit the loop.

import time
from func_timeout import func_set_timeout , FunctionTimedOut

stop = False
@func_set_timeout(3)
def test():
    while True:
        time.sleep(1)
        if stop == True :
            break
        print("test")
            
def test2():
    global stop
    try :
        test()
    except FunctionTimedOut:
        stop = True
        print("end test!!")
        
test2()

Utsav13 avatar Nov 29 '19 09:11 Utsav13

You have found a real bug! This works in a file in python2, and works in python3 both interactive and with a file. The problem is interactive mode in python2, and here is the problem: The thread is marked dead, but still executing! (This is a bug in python2, hence why it acts differently in python3).

Here is my output for running the same in a file:

$ python2 2>&1 |  head -n1 & sleep .5; kill -9 %%
[2] 13940
Python 2.7.14 (default, Oct 31 2017, 21:12:13)

$ cat test.py
#!/usr/bin/python2

import time

from func_timeout import func_set_timeout

@func_set_timeout(3)
def test():
    while True:
        time.sleep(1)
        print ( "Test" )

if __name__ == '__main__':

    test()

And the COMPLETE output:

$ ./test.py
Test
Test
Traceback (most recent call last):
  File "./test.py", line 16, in <module>
    test()
  File "/home/tim/projects/func_timeout/func_timeout/dafunc.py", line 185, in <lambda>
    return wraps(func)(lambda *args, **kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs))
  File "/home/tim/projects/func_timeout/func_timeout/dafunc.py", line 101, in func_timeout
    raise FunctionTimedOut('', timeout, func, args, kwargs)

func_timeout.exceptions.FunctionTimedOut: Function test (args=()) (kwargs={}) timed out after 3.000000 seconds.

Caveat

I AM able to reproduce this by running the exact same code in the python2 interactive terminal, however it does NOT reproduce in a standalone file.

This appears to be a bug in python, see the output of the threading.enumerate() call, which lists all active threads:

[<_MainThread(MainThread, started 25769803792)>, <StoppableThread(Thread-1, stopped daemon 25771807856)>]

Show that it is not alive:

threading.enumerate()[1].isAlive()

False

So.... there is obviously some bug in python2 here, where it is still running a thread that it has internally marked as dead, and is not cleaning it up.

Trying the same in python3 interactive terminal works as expected:

>>> test()
Test
Test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/tim/projects/func_timeout/func_timeout/dafunc.py", line 185, in <lambda>
    return wraps(func)(lambda *args, **kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs))
  File "/home/tim/projects/func_timeout/func_timeout/dafunc.py", line 101, in func_timeout
raise FunctionTimedOut('', timeout, func, args, kwargs)

func_timeout.exceptions.FunctionTimedOut: Function test (args=()) (kwargs={}) timed out after 3.000000 seconds.

And as you can see, the thread is properly cleaned-up.

>>> import threading
>>> threading.enumerate()
[<_MainThread(MainThread, started 25769803792)>]

I tested the following patch, which seems to stop the thread after 6 seconds (instead of the expected 3). Note in this case I reduced "repeatEvery" / "raiseEvery" from 2.0 seconds to .25 seconds for brevity sake.

diff --git a/func_timeout/StoppableThread.py b/func_timeout/StoppableThread.py
index 9b8bb3b..4e9c116 100644
--- a/func_timeout/StoppableThread.py
+++ b/func_timeout/StoppableThread.py
@@ -119,11 +120,29 @@ class JoinThread(threading.Thread):
         if hasattr(self.otherThread, '_Thread__stop'):
             # If py2, call this first to start thread termination cleanly.
             #   Python3 does not need such ( nor does it provide.. )
+            print ( "IsAlive1? " + str(self.otherThread.is_alive()) )
             self.otherThread._Thread__stop()
+            print ( "IsAlive2? " + str(self.otherThread.is_alive()) )
+            self.otherThread.join(.1)
+            print ( "IsAlive3? " + str(self.otherThread.is_alive()) )
+            ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.otherThread.ident), ctypes.py_object(self.exception))
+            print ( "IsAlive4? " + str(self.otherThread.is_alive()) )
+            self.otherThread.join(.1)
+            print ( "IsAlive5? " + str(self.otherThread.is_alive()) )
+            self.otherThread._Thread__stop()
+            print ( "IsAlive6? " + str(self.otherThread.is_alive()) )
+            self.otherThread.join(.1)
+            print ( "IsAlive7? " + str(self.otherThread.is_alive()) )
+            self.otherThread.join(self.repeatEvery)
+            print ( "IsAlive8? " + str(self.otherThread.is_alive()) )
+
         while self.otherThread.is_alive():
+            print ( "IsAlive9? " + str(self.otherThread.is_alive()) )
             # We loop raising exception incase it's caught hopefully this breaks us far out.
             ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.otherThread.ident), ctypes.py_object(self.exception))
+            print ( "IsAlive10? " + str(self.otherThread.is_alive()) )
             self.otherThread.join(self.repeatEvery)
+            print ( "IsAlive11? " + str(self.otherThread.is_alive()) )

And you can see the results:

Test Test IsAlive1? True IsAlive2? False IsAlive3? False IsAlive4? False IsAlive5? False IsAlive6? False IsAlive7? False IsAlive8? False Traceback (most recent call last): File "", line 1, in File "func_timeout/dafunc.py", line 186, in return wraps(func)(lambda *args, **kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs)) File "func_timeout/dafunc.py", line 101, in func_timeout raise FunctionTimedOut('', timeout, func, args, kwargs) func_timeout.exceptions.FunctionTimedOut: Function doit (args=()) (kwargs={}) timed out after 3.000000 seconds.

Test Test Test Test

So, even though it is marked dead, apparently hammering it with exceptions and joins finally works around the bug and makes it die.

I will have to find a clean way to accomplish this for python2 (which is just about EOL, btw...), as the above patch obviously isn't acceptable. I would suggest that you use python3 for now.

I am not sure if this is limited to the print function, or if other things (such as math) retain the running.

Stay tuned!

kata198 avatar Dec 02 '19 23:12 kata198

`@func_set_timeout(7) def myloop(n): for i in range(n): print (i) time.sleep(1)

try: myloop(10) except FunctionTimedOut: print ('I timed out')

print ('************')

try: func_timeout(5, myloop, args=[10]) except FunctionTimedOut: print ('I timed out') `

Output: 0 1 2 3 4 5 6 I timed out


0 1 2 3 4 5 I timed out

6 7 8 9

I use python 3.7 and this issue is still there. I've embedded the myloop function within the func_set_timeout decorator with 7 seconds timeout. In first case, everything works fine when I call this function as it is now. In the second case I call it via the func_timeout wrapper but with 5 seconds timeout this time. I would have expected the function to timeout at 5 seconds. It does raise the FunctionTimedOut exception at 5 secs, but the thread keeps going, and this time the func_set_timeout is completely ignored. Looks like a more fundamental issue than just the python version. Many thanks for your help.

dipanjanc5 avatar Feb 11 '20 16:02 dipanjanc5

This is still an issue with Python 3.6, which sadly renders the library pretty much useless :(

JurajMa avatar Oct 15 '20 10:10 JurajMa

on python 3.10 the bug mentioned is fixed, 0 1 2 3 4 5 I timed out

however


import time    
import func_timeout
from func_timeout import func_set_timeout

@func_set_timeout(1)
def jls_extract_def():
    return print(eval(s))
s='4.10**15**10**9'
try:
    jls_extract_def()
except func_timeout.exceptions.FunctionTimedOut:
    print('out_time')# 
    # return 1

eval can not be interupted correctly

jijivski avatar Nov 12 '23 10:11 jijivski