@@ -11,6 +11,7 @@ class ParallelProcessing: ...
11
11
12
12
import sys
13
13
import time
14
+ import ctypes
14
15
import signal
15
16
import threading
16
17
from functools import wraps
@@ -32,7 +33,7 @@ class ParallelProcessing: ...
32
33
HookFunction ,
33
34
)
34
35
from typing_extensions import Generic , ParamSpec
35
- from typing import List , Callable , Optional , Union , Mapping , Sequence , Tuple , Generator
36
+ from typing import List , Optional , Union , Mapping , Sequence , Tuple , Generator
36
37
37
38
38
39
Threads : set ['Thread' ] = set ()
@@ -56,7 +57,6 @@ class Thread(threading.Thread, Generic[_Target_P, _Target_T]):
56
57
57
58
# threading.Thread stuff
58
59
_initialized : bool
59
- _run : Callable
60
60
61
61
def __init__ (
62
62
self ,
@@ -116,23 +116,29 @@ def _wrap_target(
116
116
def wrapper (
117
117
* args : _Target_P .args , ** kwargs : _Target_P .kwargs
118
118
) -> Union [_Target_T , None ]:
119
- self .status = 'Running'
119
+ try :
120
+ self .status = 'Running'
120
121
121
- global Threads
122
- Threads .add (self )
122
+ global Threads
123
+ Threads .add (self )
123
124
124
- try :
125
- self ._returned_value = target (* args , ** kwargs )
126
- except Exception as e :
127
- if not any (isinstance (e , ignore ) for ignore in self .ignore_errors ):
128
- self .status = 'Errored'
129
- self .errors .append (e )
130
- return
125
+ try :
126
+ self ._returned_value = target (* args , ** kwargs )
127
+ except Exception as e :
128
+ if not any (isinstance (e , ignore ) for ignore in self .ignore_errors ):
129
+ self .status = 'Errored'
130
+ self .errors .append (e )
131
+ return
132
+
133
+ self .status = 'Invoking hooks'
134
+ self ._invoke_hooks ()
135
+ Threads .remove (self )
136
+ self .status = 'Completed'
131
137
132
- self . status = 'Invoking hooks'
133
- self ._invoke_hooks ()
134
- Threads . remove ( self )
135
- self . status = 'Completed'
138
+ except SystemExit :
139
+ self .status = 'Killed'
140
+ print ( 'KILLED ident: %s' % self . ident )
141
+ return
136
142
137
143
return wrapper
138
144
@@ -157,27 +163,6 @@ def _handle_exceptions(self) -> None:
157
163
for e in self .errors :
158
164
raise e
159
165
160
- def global_trace (self , frame , event : str , arg ) -> Optional [Callable ]:
161
- if event == 'call' :
162
- return self .local_trace
163
-
164
- def local_trace (self , frame , event : str , arg ):
165
- if self .status == 'Kill Scheduled' and event == 'line' :
166
- print ('KILLED ident: %s' % self .ident )
167
- self .status = 'Killed'
168
- raise SystemExit ()
169
- return self .local_trace
170
-
171
- def _run_with_trace (self ) -> None :
172
- """This will replace `threading.Thread`'s `run()` method"""
173
- if not self ._run :
174
- raise exceptions .ThreadNotInitializedError (
175
- 'Running `_run_with_trace` may cause unintended behaviour, run `start` instead'
176
- )
177
-
178
- sys .settrace (self .global_trace )
179
- self ._run ()
180
-
181
166
@property
182
167
def result (self ) -> _Target_T :
183
168
"""
@@ -274,17 +259,33 @@ def kill(self, yielding: bool = False, timeout: float = 5) -> bool:
274
259
275
260
Returns
276
261
-------
277
- :returns bool: False if the it exceeded the timeout
262
+ :returns bool: False if the it exceeded the timeout without being killed
278
263
279
264
Raises
280
265
------
266
+ ValueError: If the thread ident does not exist
281
267
ThreadNotInitializedError: If the thread is not initialized
282
268
ThreadNotRunningError: If the thread is not running
283
269
"""
284
270
if not self .is_alive ():
285
271
raise exceptions .ThreadNotRunningError ()
286
272
287
273
self .status = 'Kill Scheduled'
274
+
275
+ res : int = ctypes .pythonapi .PyThreadState_SetAsyncExc (
276
+ ctypes .c_long (self .ident ), ctypes .py_object (SystemExit )
277
+ )
278
+
279
+ if res == 0 :
280
+ raise ValueError ('Thread IDENT does not exist' )
281
+ elif res > 1 :
282
+ # Unexpected behaviour, something seriously went wrong
283
+ # https://docs.python.org/3/c-api/init.html#c.PyThreadState_SetAsyncExc
284
+ ctypes .pythonapi .PyThreadState_SetAsyncExc (self .ident , None )
285
+ raise SystemError (
286
+ f'Killing thread with ident [{ self .ident } ] failed!\n PyThreadState_SetAsyncExc returned: { res } '
287
+ )
288
+
288
289
if not yielding :
289
290
return True
290
291
@@ -308,8 +309,6 @@ def start(self) -> None:
308
309
if self .is_alive ():
309
310
raise exceptions .ThreadStillRunningError ()
310
311
311
- self ._run = self .run
312
- self .run = self ._run_with_trace
313
312
super ().start ()
314
313
315
314
0 commit comments