Killable Threads August 13, 2006
The thread2
module is an extension of the standard threading
module, and provides the means
to raise exceptions at the context of the given thread. You can use raise_exc()
to raise an
arbitrary exception, or call terminate()
to raise SystemExit
automatically.
It uses the unexposed PyThreadState_SetAsyncExc
function (via ctypes
) to raise an exception
in the context of the given thread. Inspired by the code of Antoon Pardon at
http://mail.python.org/pipermail/python-list/2005-December/316143.html.
Issues
- The exception will be raised only when executing python bytecode. If your thread calls a
native/built-in blocking function, the exception will be raised only when execution returns to
the python code.
- There is also an issue if the built-in function internally calls
PyErr_Clear()
, which would effectively cancel your pending exception. You can try to raise it again.
- There is also an issue if the built-in function internally calls
- Only exception types can be raised safely. Exception instances are likely to cause
unexpected behavior, and are thus restricted.
- For example:
t1.raise_exc(TypeError)
and nott1.raise_exc(TypeError("blah"))
. - IMHO it’s a bug, and I reported it as one. For more info, http://mail.python.org/pipermail/python-dev/2006-August/068158.html
- For example:
- I asked to expose this function in the built-in
thread
module, but sincectypes
has become a standard library (as of 2.5), and this feature is not likely to be implementation-agnostic, it may be kept unexposed.
Code
import threading
import inspect
import ctypes
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
if not inspect.isclass(exctype):
raise TypeError("Only types can be raised (not instances)")
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
raise SystemError("PyThreadState_SetAsyncExc failed")
class Thread(threading.Thread):
def _get_my_tid(self):
"""determines this (self's) thread id"""
if not self.isAlive():
raise threading.ThreadError("the thread is not active")
# do we have it cached?
if hasattr(self, "_thread_id"):
return self._thread_id
# no, look for it in the _active dict
for tid, tobj in threading._active.items():
if tobj is self:
self._thread_id = tid
return tid
raise AssertionError("could not determine the thread's id")
def raise_exc(self, exctype):
"""raises the given exception type in the context of this thread"""
_async_raise(self._get_my_tid(), exctype)
def terminate(self):
"""raises SystemExit in the context of the given thread, which should
cause the thread to exit silently (unless caught)"""
self.raise_exc(SystemExit)
Example
>>> import time
>>> from thread2 import Thread
>>>
>>> def f():
... try:
... while True:
... time.sleep(0.1)
... finally:
... print "outta here"
...
>>> t = Thread(target = f)
>>> t.start()
>>> t.isAlive()
True
>>> t.terminate()
>>> t.join()
outta here
>>> t.isAlive()
False