@@ -51,6 +51,79 @@ class TimeoutError(Exception): # NOQA
5151NO_ERROR = None
5252NO_VALUE = None
5353
54+ from ctypes import c_long
55+ from ctypes import py_object
56+ from ctypes import pythonapi
57+ from multiprocessing import TimeoutError as MpTimeoutError
58+
59+ from queue import Empty as Queue_Empty
60+ from queue import Queue
61+ from _thread import start_new_thread
62+
63+ def async_raise (tid , exctype = Exception ):
64+ """
65+ Raise an Exception in the Thread with id `tid`. Perform cleanup if
66+ needed.
67+
68+ Based on Killable Threads By Tomer Filiba
69+ from http://tomerfiliba.com/recipes/Thread2/
70+ license: public domain.
71+ """
72+ assert isinstance (tid , int ), 'Invalid thread id: must an integer'
73+
74+ tid = c_long (tid )
75+ exception = py_object (Exception )
76+ res = pythonapi .PyThreadState_SetAsyncExc (tid , exception )
77+ if res == 0 :
78+ raise ValueError ('Invalid thread id.' )
79+ elif res != 1 :
80+ # if it returns a number greater than one, you're in trouble,
81+ # and you should call it again with exc=NULL to revert the effect
82+ pythonapi .PyThreadState_SetAsyncExc (tid , 0 )
83+ raise SystemError ('PyThreadState_SetAsyncExc failed.' )
84+
85+ def thread_interruptible (func , args = None , kwargs = None , timeout = DEFAULT_TIMEOUT ):
86+ """
87+ Threads-based interruptible runner. It can work on both Windows and POSIX,
88+ but is not reliable and works only if everything is pickable.
89+ """
90+ # We run `func` in a thread and block on a queue until timeout
91+ results = Queue ()
92+
93+ def runner ():
94+ """
95+ Run the func and send results back in a queue as a tuple of
96+ (`error`, `value`)
97+ """
98+ try :
99+ _res = func (* (args or ()), ** (kwargs or {}))
100+ results .put ((NO_ERROR , _res ,))
101+ except Exception :
102+ results .put ((ERROR_MSG + traceback_format_exc (), NO_VALUE ,))
103+
104+ tid = start_new_thread (runner , ())
105+
106+ try :
107+ # wait for the queue results up to timeout
108+ err_res = results .get (timeout = timeout )
109+
110+ if not err_res :
111+ return ERROR_MSG , NO_VALUE
112+
113+ return err_res
114+
115+ except (Queue_Empty , MpTimeoutError ):
116+ return TIMEOUT_MSG % locals (), NO_VALUE
117+
118+ except Exception :
119+ return ERROR_MSG + traceback_format_exc (), NO_VALUE
120+
121+ finally :
122+ try :
123+ async_raise (tid , Exception )
124+ except (SystemExit , ValueError ):
125+ pass
126+
54127if not on_windows :
55128 """
56129 Some code based in part and inspired from the RobotFramework and
@@ -93,92 +166,23 @@ def handler(signum, frame):
93166 except TimeoutError :
94167 return TIMEOUT_MSG % locals (), NO_VALUE
95168
96- except Exception :
169+ except ValueError as ve :
170+ if 'signal only works in main thread' in str (ve ):
171+ # Fallback to the thread-based implementation if we are not in the main thread of the main interpreter
172+ return thread_interruptible (func , args , kwargs , timeout )
97173 return ERROR_MSG + traceback_format_exc (), NO_VALUE
98174
99- finally :
100- setitimer (ITIMER_REAL , 0 )
101-
102- elif on_windows :
103- """
104- Run a function in an interruptible thread with a timeout.
105- Based on an idea of dano "Dan O'Reilly"
106- http://stackoverflow.com/users/2073595/dano
107- But no code has been reused from this post.
108- """
109-
110- from ctypes import c_long
111- from ctypes import py_object
112- from ctypes import pythonapi
113- from multiprocessing import TimeoutError as MpTimeoutError
114-
115- from queue import Empty as Queue_Empty
116- from queue import Queue
117- from _thread import start_new_thread
118-
119- def interruptible (func , args = None , kwargs = None , timeout = DEFAULT_TIMEOUT ):
120- """
121- Windows, threads-based interruptible runner. It can work also on
122- POSIX, but is not reliable and works only if everything is pickable.
123- """
124- # We run `func` in a thread and block on a queue until timeout
125- results = Queue ()
126-
127- def runner ():
128- """
129- Run the func and send results back in a queue as a tuple of
130- (`error`, `value`)
131- """
132- try :
133- _res = func (* (args or ()), ** (kwargs or {}))
134- results .put ((NO_ERROR , _res ,))
135- except Exception :
136- results .put ((ERROR_MSG + traceback_format_exc (), NO_VALUE ,))
137-
138- tid = start_new_thread (runner , ())
139-
140- try :
141- # wait for the queue results up to timeout
142- err_res = results .get (timeout = timeout )
143-
144- if not err_res :
145- return ERROR_MSG , NO_VALUE
146-
147- return err_res
148-
149- except (Queue_Empty , MpTimeoutError ):
150- return TIMEOUT_MSG % locals (), NO_VALUE
151-
152175 except Exception :
153176 return ERROR_MSG + traceback_format_exc (), NO_VALUE
154177
155178 finally :
156179 try :
157- async_raise ( tid , Exception )
158- except ( SystemExit , ValueError ) :
180+ setitimer ( ITIMER_REAL , 0 )
181+ except ValueError :
159182 pass
160183
161- def async_raise (tid , exctype = Exception ):
162- """
163- Raise an Exception in the Thread with id `tid`. Perform cleanup if
164- needed.
165-
166- Based on Killable Threads By Tomer Filiba
167- from http://tomerfiliba.com/recipes/Thread2/
168- license: public domain.
169- """
170- assert isinstance (tid , int ), 'Invalid thread id: must an integer'
171-
172- tid = c_long (tid )
173- exception = py_object (Exception )
174- res = pythonapi .PyThreadState_SetAsyncExc (tid , exception )
175- if res == 0 :
176- raise ValueError ('Invalid thread id.' )
177- elif res != 1 :
178- # if it returns a number greater than one, you're in trouble,
179- # and you should call it again with exc=NULL to revert the effect
180- pythonapi .PyThreadState_SetAsyncExc (tid , 0 )
181- raise SystemError ('PyThreadState_SetAsyncExc failed.' )
184+ elif on_windows :
185+ interruptible = thread_interruptible
182186
183187
184188def fake_interruptible (func , args = None , kwargs = None , timeout = DEFAULT_TIMEOUT ):
0 commit comments