[capi-sig] Question about proper GIL management for a language interoperability tool

Tom Epperly tepperly at llnl.gov
Fri Jan 11 19:14:36 CET 2008


I working on a language interoperability tool called Babel, 
https://computation.llnl.gov/casc/components/. It provides 
high-performance, bi-directional communication between any combination 
of C, C++, F77, F90, Java, and Python. Right now, I am trying to make 
sure I am handling the GIL locking and unlocking correctly, and I am 
wondering if the Python community can confirm whether I am handling 
things appropriately.

Babel handles the many-to-many language interoperability problem with a 
hub and spoke architecture, so all inter-language calls go through C. 
Hence, I can discuss how I am handling the GIL in terms of 
communications between C and Python. I am going to describe what I am 
doing using pseudo-code and leaving out irrelevant details.

CASE 1 (some other language calling Python via C)
========================================
foo_Factorial(struct foo_t *self, int32_t arg) {
  /* C routine that calls Python */
  PyGILState_STATE _gstate;
  _gstate = PyGILState_Ensure();
  /* convert incoming arguments from C to Python */
  _result = PyObject_CallObject(_pfunc, _args);
  /* process outgoing arguments or exceptions */
  PyGILState_Release(_gstate);
  /* return to caller in whatever language */
}

CASE 2 (Python calling some other language via C)
========================================
static PyObject *
pStub_foo_Factorial(PyObject *_self, PyObject *_args, PyObject *_kwdict) {
  PyObject *result;
  /* unpack incoming Python arguments into their C equivalents */
  Py_BEGIN_ALLOW_THREADS
  /* dispatch to method implementation */
  foo_Factorial(i,k,l); /* this step is actually done through a function 
pointer */
  Py_END_ALLOW_THREADS
  /* pack outgoing C arguments into the Python return value  or process 
any exceptions */
  return result;
}

Discussion
========================================
Case 1 seems pretty straight forward. The current thread must own the 
GIL before making any Python C API calls, and it should release it when 
it's done. Case 2 also seems appropriate because function x_y_Method 
could very well do blocking I/O operation. For example, Babel also 
supports remote method invocation (RMI), so x_y_Method might dispatch 
the method across the network to another machine.

In ceval.h where Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS are 
defined, it says "WARNING: NEVER NEST CALLS TO Py_BEGIN_ALLOW_THREADS 
AND Py_END_ALLOW_THREADS!!!".  This makes me wonder if there is an issue 
with Python calling itself  through Babel. Let's assume that Python is 
the main driver, and it calls a Babel wrapped Python method to calculate 
factorial or something. This will result in nested calls to 
PyGILState_Ensure/PyGILState_Release and 
Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS.

Python interactive shell (invokes foo.Factorial(3))
  Py_BEGIN_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
    PyGILState_Ensure (in foo_Factorial (case 1) arg=3)
      Py_BEGIN_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
        PyGILState_Ensure (in foo_Factorial (case 1) arg=2)
          Py_BEGIN_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
            PyGILState_Ensure (in foo_Factorial (case 1) arg=1)
            PyGILState_Release (in foo_Factorial (case 1) arg=1)
          Py_END_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
        PyGILState_Release (in foo_Factorial (case 1) arg=2)
      Py_END_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
    PyGILState_Release (in foo_Factorial (case 1) arg=3)
  Py_END_ALLOW_THREADS (in pStub_foo_Factorial (case 2))
Python interactive shell

Is this approach correct?

Regards,

Tom Epperly
LLNL/CASC


More information about the capi-sig mailing list