OOF2: The Manual

Name

acquirePyLock — ensure thread-safe Python API calls

Synopsis

#include "common/threadstate.h" 
void acquirePyLock ( );  
 
void releasePyLock ( );  
 
#include "common/ooferror.h" 
void pythonErrorRelay ( );  
 

Description

The three functions described here are used whenever OOF2 C++ code calls Python API functions that might invoke the Python interpreter. It's often difficult to tell when the interpreter will be invoked, so it's a good idea to treat all Python API calls in this way. See http://docs.python.org/ext/thinIce.html for a relevant discussion (although in a different context).

Because the Python interpreter is not thread safe, whenever a C++ function in OOF2 needs to call the Python API, it must first acquire the Python global interpreter lock by calling acquirePyLock(). When it's through with the Python API calls, it must release the lock by calling releasePyLock().

A function that neglects to call acquirePyLock() risks crashing the Python interpreter. A function that neglects to call releasePyLock will probably deadlock the program when the C++ routine finishes. If running OOF2 with the --unthreaded option solves a crashing or deadlocking problem, then check the code for missing calls to acquirePyLock and releasePyLock.

It is important to ensure that releasePyLock is called even if an exception is thrown during the Python API calls. Python API calls should always occur within a try ... catch block, like this:

#include "common/threadstate.h"

   acquirePyLock();
   try {
      // Call Python API
   }
   catch (...) {
      releasePyLock();
      throw;
   }
   releasePyLock(); 

It's possible that calls to the Python API will raise a Python exception but not throw a C++ exception. Generally, if a Python API function returns NULL, an exception has been raised. The OOF2 function pythonErrorRelay ensures that Python exceptions are re-raised in Python when the C++ function exits. It should be used like this:

#include "common/ooferror.h"
#include "common/threadstate.h"

   acquirePyLock();
   try {
      PyObject *func, *args; // assume these have been set
      PyObject *result = PyEval_Call_Object(func, args); // for example 
      if(result == NULL) 
         pythonErrorRelay();
      // do something with result
   }
   catch (...) {
      releasePyLock();
      throw;
   }
   releasePyLock(); 

pythonErrorRelay raises a C++ exception that will be converted back into a Python exception when control returns to Python. The mechanism even works if the Python exception was caused by a C++ exception that occurred during a second call back into C++, as long as the original C++ exception was derived from the OOF2 ErrError class.