@@ -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
@@ -116,23 +117,29 @@ def _wrap_target(
116
117
def wrapper (
117
118
* args : _Target_P .args , ** kwargs : _Target_P .kwargs
118
119
) -> Union [_Target_T , None ]:
119
- self .status = 'Running'
120
+ try :
121
+ self .status = 'Running'
120
122
121
- global Threads
122
- Threads .add (self )
123
+ global Threads
124
+ Threads .add (self )
123
125
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
126
+ try :
127
+ self ._returned_value = target (* args , ** kwargs )
128
+ except Exception as e :
129
+ if not any (isinstance (e , ignore ) for ignore in self .ignore_errors ):
130
+ self .status = 'Errored'
131
+ self .errors .append (e )
132
+ return
133
+
134
+ self .status = 'Invoking hooks'
135
+ self ._invoke_hooks ()
136
+ Threads .remove (self )
137
+ self .status = 'Completed'
131
138
132
- self . status = 'Invoking hooks'
133
- self ._invoke_hooks ()
134
- Threads . remove ( self )
135
- self . status = 'Completed'
139
+ except SystemExit :
140
+ self .status = 'Killed'
141
+ print ( 'KILLED ident: %s' % self . ident )
142
+ return
136
143
137
144
return wrapper
138
145
@@ -157,27 +164,6 @@ def _handle_exceptions(self) -> None:
157
164
for e in self .errors :
158
165
raise e
159
166
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
167
@property
182
168
def result (self ) -> _Target_T :
183
169
"""
@@ -285,6 +271,20 @@ def kill(self, yielding: bool = False, timeout: float = 5) -> bool:
285
271
raise exceptions .ThreadNotRunningError ()
286
272
287
273
self .status = 'Kill Scheduled'
274
+ res : int = ctypes .pythonapi .PyThreadState_SetAsyncExc (
275
+ ctypes .c_long (self .ident ), ctypes .py_object (SystemExit )
276
+ )
277
+
278
+ if res == 0 :
279
+ raise ValueError ('Thread IDENT does not exist' )
280
+ elif res > 1 :
281
+ # Unexpected behaviour, something seriously went wrong
282
+ # https://docs.python.org/3/c-api/init.html#c.PyThreadState_SetAsyncExc
283
+ ctypes .pythonapi .PyThreadState_SetAsyncExc (self .ident , None )
284
+ raise SystemError (
285
+ f'Killing thread with ident [{ self .ident } ] failed!\n PyThreadState_SetAsyncExc returned: { res } '
286
+ )
287
+
288
288
if not yielding :
289
289
return True
290
290
@@ -308,8 +308,6 @@ def start(self) -> None:
308
308
if self .is_alive ():
309
309
raise exceptions .ThreadStillRunningError ()
310
310
311
- self ._run = self .run
312
- self .run = self ._run_with_trace
313
311
super ().start ()
314
312
315
313
0 commit comments