[Pyrex] Callbacks from other threads (crash in PyGILState_Release)
Geoff Schmidt
gschmidt at gschmidt.org
Tue Jun 20 15:08:30 UTC 2006
Good morning fellow Pyrexians,
I'm trying to write a Pyrex extension that calls into a multithreaded
C library. I want to expose the ability to register a Python function
as a callback with the C library, but the C library creates its own
processing threads and delivers callbacks on them -- so I can't
guarantee that the callback will happen on the thread that registered
the callback, or that it will happen on a thread that Pyrex has even
seen before.
I figured that I just needed to wrap the callback function in
PyGILState_Ensure / PyGILState_Release. When I do that, the Python
code in the callback does run, but the process segfaults in the call
to PyGILState_Release! It turns out that the minimal case of creating
a thread with pthread_create and then calling PyGILState_Ensure /
PyGILState_Release in the new thread is sufficient to get the crash
(100% of the time.)
I took a look at the crash and as best I can tell the global
head_mutex in pystate.c is getting corrupted somewhere in the process
of PyThreadState_DeleteCurrent trying to take the tstate out of the
linked list of tstates, but I don't know anything about this code and
it's not obvious to me what's going on. I posted a little more detail
to comp.lang.python.
Is this a bug in Python? Is there a way around it (short of leaving a
Python-blessed thread blocking on a condition variable and passing
Python functions to it to call?) Or am I just doing it wrong?
This is the official 2.4.3 build under OS.X 10.4.6 on PPC. Here's a
test case. I run it as 'setup foo.pyx build_ext --inplace', 'python',
'import foo', 'foo.callFuncInThread()'. This crashes. On the other
hand, 'foo.callFuncDirectly()' does not. In the real program, the
callback trampoline and the PyGILState_XXX are in pure C stub code
and it still crashes, so it doesn't appear to be a problem with Pyrex
emitting initialization code before the thread state is set up, which
was the other mention I saw in the list archives.
--- snip (foo.pyx) ---
cdef extern from "stdio.h":
int printf(char *str, ...)
cdef extern from "Python.h":
ctypedef int PyGILState_STATE
PyGILState_STATE PyGILState_Ensure()
void PyGILState_Release(PyGILState_STATE gstate)
cdef extern from "pthread.h":
ctypedef void *pthread_t # it'll do
int pthread_create(pthread_t *thread, void *attr,
void *(*start_routine)(void *), void *arg)
cdef extern void *func(void *x):
printf("Entering func(%p)\n", x)
cdef PyGILState_STATE st
printf("PyGILState_Ensure\n")
st = PyGILState_Ensure()
printf("PyGILState_Release\n")
PyGILState_Release(st)
printf("Leaving func\n")
def callFuncDirectly():
func(NULL)
def callFuncInThread():
cdef pthread_t thr
pthread_create(&thr, NULL, func, NULL);
--- snip (setup.py) ---
from distutils.core import setup
from distutils.extension import Extension
from Pyrex.Distutils import build_ext
setup(
name = 'foo',
ext_modules = [Extension("foo", ["foo.pyx"])],
cmdclass = {'build_ext': build_ext},
)
--- end ---
Thank you very much in advance for any help or pointers.
Geoff Schmidt
gschmidt at gschmidt.org
More information about the Pyrex
mailing list