Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to stop timeout task #10

Open
fengyehong opened this issue Nov 28, 2019 · 7 comments
Open

How to stop timeout task #10

fengyehong opened this issue Nov 28, 2019 · 7 comments

Comments

@fengyehong
Copy link

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?

@Utsav13
Copy link

Utsav13 commented Nov 28, 2019

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!!!

@fengyehong
Copy link
Author

fengyehong commented Nov 28, 2019

@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

@Utsav13
Copy link

Utsav13 commented Nov 29, 2019

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()

@kata198
Copy link
Owner

kata198 commented Dec 2, 2019

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!

@dipanjanc5
Copy link

dipanjanc5 commented Feb 11, 2020

`@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.

@JurajMa
Copy link

JurajMa commented Oct 15, 2020

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

@jijivski
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants