Saturday, August 1, 2009

Not a nice way to interrupt a sub thread

In my application, the main thread in in charge of UI drawing with wxPython and starting worker threads which runs user defined Python code. If a user defined Python code is buggy the application user needs to stop it. There's a way to raise a KeyboardInterrupt exception in the main thread but unfortunately not the opposite. What I needed to do is interrupting a sub thread from the main thread. I don't know why it doesn't exist. Probably it is a well considered decision but what I need is what Python misses. The API provides a function PyThreadState_SetAsyncExc() which takes a thread id and exception object and raises exception in the thread. So I had to make a wrapper extension.

pySubthreadInterruptTest.c

#include <Python.h>

static PyObject* interrupt(PyObject* self, PyObject* args)
{
    long threadid;
    PyObject* po_exception;
    if(! PyArg_ParseTuple(args, "lO", &threadid, &po_exception))
    {
        return NULL;
    }

    int result = PyThreadState_SetAsyncExc(threadid, po_exception);
    return Py_BuildValue("l", result);
}

static PyMethodDef MethodsDefs[] = {
    {"interrupt", interrupt, METH_VARARGS},
    {NULL, NULL, 0},
};

void initpystit(void){
    (void) Py_InitModule("pystit", MethodsDefs);
}

setup.py
from distutils.core import setup, Extension

module1 = Extension('pystit',
                    sources = ['pySubthreadInterruptTest.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

test.py
import time, thread
import pystit
def f():
    print 'thread start'
    try:
            for i in range(1000):
                    print i
                    time.sleep(0.001)
    except:
            print 'interrupted'
            raise
    print 'thread end'
tid = thread.start_new_thread(f, ())
time.sleep(0.001)
pystit.interrupt(tid, ValueError)
time.sleep(1)

$ python setup.py build
running build
running build_ext
building 'pystit' extension
gcc -pthread -shared build/temp.linux-i686-2.6/pySubthreadInterruptTest.o -L/usr/lib -lpython2.6 -o build/lib.linux-i686-2.6/pystit.so
$ cd build/lib.linux-i686-2.6/
$ ls
pystit.so  test.py
$ python test.py
thread start
0
1
2
3
interrupted
Unhandled exception in thread started by <function f at 0xb8043e64>
Traceback (most recent call last):
  File "test.py", line 8, in f
    time.sleep(0.001)
ValueError
$ 

It works but I can't say I'm quite satisfied with the solution.

No comments: