02 July
2007
Digg digg it  |  Slashdot slashdot.org  |  Reddit reddit  |  del.icio.us del.icio.us  |  Technorati Technorati

On how I've just killed a Python thread...

Lurking around the (SVN) Paste source code I've found a module named "killthread.py" that does just what it says; it kills a thread!!!

The module code comes from the "thread2 recipe" provided gently by Tomer Filiba (great recipes!). It needs ctypes installed (standard from Python 2.5) and has some small issues, but it works! I think this should be integral part of the standard Python threading library.

Update

Here you can find a patch file I made against the standard (Python 2.4.4) threading module. I've tested it under Windows, Linux and OS X.To use it:

patch -b -i python_2.4.4_threading_kill.diff threading.py



The Recipe

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)


Category Python 
Posted by alex at 15:28 | Comments (0) | Trackbacks (0)
<< The rise of another Web Framework (?) | Main | Event Horizon: Devil Framework's event management system >>
Comments
Re: On how I've just killed a Python thread...

There is some disagreement about this particular feature in the language design community. Java says it's bad, because it's impossible to write correct programs if you do this:

http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html

Microsoft, on the other hand, takes the position "who is trying to write correct programs anyway?" and allows it anyway:

http://msdn2.microsoft.com/en-us/library/aa332365(VS.71).aspx

Python seems inclined to follow in Java's footsteps here:

http://mail.python.org/pipermail/patches/2001-August/005668.html

which I'm personally happy about.

If you want to be able to terminate a process in anything resembling safety, you need isolation. Threads don't have isolation. If you want this functionality, why not start a subprocess? That will also get you the ability to use multiple processors more effectively.

Posted by: Glyph Lefkowitz at July 02,2007 18:15
Re to Glyph

I totally agree with you about isolation, etc. but there are times that you can't go that way and you really need to terminate a non-responsive thread from "outside".

A sample case (that we had to resolve in our system) follows.

You have an application with an hot-swappable plugin sub-system.

You install a plugin. In this plugin you want to perform a simple but long-running task without blocking the main GUI thread and you need the share some state/data/api (so a subproces spawn is not a viable solution).

You just want to trigger "threading.Thread (target=func)" and forget about it. The "func" is simple and can not block.

BUT IT BLOCKS @!#!

If you can not terminate the thread you can not install/swap a new working version of the incriminated plugin.

I know this is an extreme case, but I think that a "Thread.kill ()" method HAS to be used in extreme cases only.

And extreme cases happen, and programming languages must not get in the way of "extreme" programmers ;-)

Posted by: Alex at July 02,2007 18:43
Re: On how I've just killed a Python thread...

As a programmer who prefers Python for many tasks I feel I can say concurrency is not fun to implement in Python (this would go for java too, I assume). On the one hand, I think it's fair to say that most problems *should not* be solved with concurrency, it's just overkill. However on the other hand, since the implementation of threading in python/java is so complex and hard to debug/test/maintain why not use another language?

In fact, Erlang is just such a language that offers a much much simpler threading paradigm [1]. In a nutshell: threads are isolated by default. They do not share memory, instead they send and receive messages. This right here has inspired me to start experimenting with Erlang for some of those rare problems where concurrency is a good solution (i.e. a heavily demanded http service, a resource intensive data crunching algorithm, etc)

[1] http://www.ibm.com/developerworks/java/library/j-cb04186.html

Posted by: Kumar McMillan at July 02,2007 20:50
Re: On how I've just killed a Python thread...

Sure, in correct code you won't need this. I added it to Paste because I wanted to be able to handle code that is not correct. Any system that only works when all the code in the system is "correct" is a bad system. In the case of Python and threads, it was a situation where a hung thread was very hard to get rid of, and even a process with a hung thread is very hard to get rid of -- the whole process can hang as it is trying to die.

You could argue that subprocesses are simpler to handle in this case. And that might be true, but threads *almost* work well enough in many cases. This makes it at least a bit easier to see what's going on when they don't work.

As for what it does, so far it's seemed very safe. SystemExit is raised, any finally blocks are called, if you are handling exceptions then any except blocks work. If configured for it, in Paste you get mailed the exception report, etc. So I'm pretty happy with how it's been working.

Posted by: Ian Bicking at July 02,2007 22:04
Re: On how I've just killed a Python thread...

Actually I would be surprised if anyone used Microsoft's Thread.Abort after reading the docs.


Calling this method usually terminates the thread.

However, if one thread calls Abort on another thread, the abort interrupts whatever code is running. There is also a chance that a static constructor could be aborted.

In the .NET Framework versions 1.0 and 1.1, there is a chance the thread could abort while a finally block is running, in which case the finally block is aborted.

If, while executing unmanaged code, a thread ignores a ThreadAbortException, the system re-throws the ThreadAbortException when the thread begins executing managed code.

The thread is not guaranteed to abort immediately, or at all.

Posted by: Jonathan Allen at July 02,2007 22:17
Trackbacks
Please send trackback to:http://www.dlevel.com/blogs/alex/20/tbping
There is no trackback.