[Python-Dev] PEP 311 Simplified Global Interpreter Lock Acquisition
for Extensions
Harri Pasanen
harri.pasanen at trema.com
Mon Aug 18 20:39:05 EDT 2003
Hi,
Just prior to my vacation I ran into a bit of trouble with my Python
embedding, as much to my dismay my C++ program that had been working
fine under Linux deadlocked under Windows.
Apparently the underlying C threading API's were behaving in subtly
different ways. After boiling down from my original embedded Python
app which features omniORB, omniORBpy, and bunch of other goodies, I
got down to the following testcase:
#include <Python.h>
class PythonLocker {
public:
PythonLocker () { _tstate = PyGILState_Ensure (); }
~PythonLocker() { PyGILState_Release(_tstate); }
}
private:
PyGILState_STATE _tstate;
};
int
main (int argc, const char **argv)
{
Py_Initialize();
PyEval_InitThreads();
PyEval_ReleaseLock (); // This is the problem
{
PythonLocker pl;
PyObject* usermod = PyImport_ImportModule ("echo");
// The previous line deadlocks on win32
}
return 0;
}
The above works fine on Linux, but deadlocks on Win32.
Tracing the execution on Win32 showed that it hanged in
thread_nt.h:
DWORD EnterNonRecursiveMutex(PNRMUTEX mutex, BOOL wait)
{
...
ret = InterlockedIncrement(&mutex->owned) ?
/* Some thread owns the mutex, let's wait... */
WaitForSingleObject(mutex->hevent, INFINITE) : WAIT_OBJECT_0 ;
The gotcha being that when entering above, mutex->owned was == -2, so
it incorrectly entered the wait.
I had a private discussion with Mark about this, and he quickly
pointed out that I should not call PyEval_ReleaseLock(). Although
this is not exactly clear from the PEP, which says
"Apart from the existing, standard Py_BEGIN_ALLOW_THREADS and
Py_END_ALLOW_THREADS macros, it is assumed that no additional thread
state API functions will be used by the extension."
I did not think PyEval_ReleaseLock() would qualify for the above, but
somehow it does, under win32 at least.
Now if I modify thread_nt.h, simple mindedly by changing the above to:
ret = InterlockedIncrement(&mutex->owned) > 0 ?
/* Some thread owns the mutex, let's wait... */
WaitForSingleObject(mutex->hevent, INFINITE) : WAIT_OBJECT_0 ;
everything works for me on Win32 as well, including python regression
tests. But I haven't wrestled with the code in question in detail to
know what gremlins that change might unleash elsewhere.
Without modifying Python, I can make my testcase working with the
following change to my test program:
{
Py_Initialize();
PyEval_InitThreads();
{ PythonLocker init_pl }; // release the lock
// PyEval_ReleaseLock ();
{ // this block could potentially be called from another thread
PythonLocker pl;
PyObject* usermod = PyImport_ImportModule ("echo");
// The previous line deadlocks on win32
}
In the C API world this is a bit more convoluted looking, as I need to
call PyGILState_Ensure(), and PyGILState_Restore, even if after
PyEval_InitThreads() I already know I have the lock.
***
Sorry for rambling, but this is in the hope that someone else can
avoid banging their heads against this as I have. And it is a bit
unclear to me also if this kind of a platform difference between
Linux and Win32 is a bug, or a feature. Ideally the Python C API
should be bug-compatible. So possibly wrong usage should cause all
platforms to fail.
Btw., the new PyGILState_* functions do not appear to be part of the
Python/C API documentation, there is only the PEP.
Anyway, thanks to Mark for this definite improvement on the state of
things.
Harri
More information about the Python-Dev
mailing list