Skip to content

Commit 651cd4c

Browse files
refactor: Utilise ctypes instead of hooking on global/local trace
1 parent 9dc7e1c commit 651cd4c

File tree

1 file changed

+35
-37
lines changed

1 file changed

+35
-37
lines changed

src/thread/thread.py

+35-37
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ParallelProcessing: ...
1111

1212
import sys
1313
import time
14+
import ctypes
1415
import signal
1516
import threading
1617
from functools import wraps
@@ -116,23 +117,29 @@ def _wrap_target(
116117
def wrapper(
117118
*args: _Target_P.args, **kwargs: _Target_P.kwargs
118119
) -> Union[_Target_T, None]:
119-
self.status = 'Running'
120+
try:
121+
self.status = 'Running'
120122

121-
global Threads
122-
Threads.add(self)
123+
global Threads
124+
Threads.add(self)
123125

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'
131138

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
136143

137144
return wrapper
138145

@@ -157,27 +164,6 @@ def _handle_exceptions(self) -> None:
157164
for e in self.errors:
158165
raise e
159166

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-
181167
@property
182168
def result(self) -> _Target_T:
183169
"""
@@ -285,6 +271,20 @@ def kill(self, yielding: bool = False, timeout: float = 5) -> bool:
285271
raise exceptions.ThreadNotRunningError()
286272

287273
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!\nPyThreadState_SetAsyncExc returned: {res}'
286+
)
287+
288288
if not yielding:
289289
return True
290290

@@ -308,8 +308,6 @@ def start(self) -> None:
308308
if self.is_alive():
309309
raise exceptions.ThreadStillRunningError()
310310

311-
self._run = self.run
312-
self.run = self._run_with_trace
313311
super().start()
314312

315313

0 commit comments

Comments
 (0)