Search code examples
pythonpython-2.7python-multithreadingpython-c-apipython-extensions

Why relasing and acquiring GIL within two threads causes an application crash?


I have developed a Python extenstion using C++. The only function of this module is something like this:

static PyObject *TestModule_API1(PyObject *self, PyObject *args)
{
   PyThreadState *_save;
   _save = PyEval_SaveThread();

   try
   {
      DoComputation1();

      PyEval_RestoreThread(_save);

   }
   catch (const std::exception & e)
   {    
      PyObject * py_exception = PyErr_NewException((char*)"pyhms.error", nullptr, nullptr);
      PyErr_SetString(py_exception, e.what());

      PyEval_RestoreThread(_save);

      return nullptr;
   }
   Py_RETURN_NONE;
}

Whenever I am calling this method with two Python threads, if the DoComputation1() method throws an exception, the application crashes. Even putting the entire try block inside a std::mutex (lock at the begining and unlock at the end of the block) does not fix the problem. What is the root reason of this problem and how should I fix it?

I am developing on Windows 10 using Visual studio 2013 and Python 2.7.

Edit 1:
If I bring the PyEval_RestoreThread(_save); line (in the catch block) to the beginning of the catch block, no crash happends. Does it mean that during the time that GIL is released, I should not call any Python API?

Edit 2:
I also need to protect my API1 method against concurrent threads using a mutex. Should I lock my mutex before releaseing the GIL of after that? Is there any case that may lead to a deadlock?


Solution

  • What is the root reason of this problem and how should I fix it?

    The root cause of the problem is that if you run DoComputation1() in two threads and this method throw an exception, both threads will run the catch block. In the catch block, some Python API functions have been used. So, it means that two threads do exist inside Python implementation that will lead to a crash.

    If I bring the PyEval_RestoreThread(_save); line (in the catch block) to the beginning of the catch block, no crash happends. Does it mean that during the time that GIL is released, I should not call any Python API?

    If you bring the PyEval_RestoreThread(_save);line to the first line of the catch block, it means that the code inside the catch block will be executed by two threads sequentially. So, no crash is happening.

    Should I lock my mutex before releaseing the GIL of after that?

    I think it is better to lock them toghether at the same time using a costruct like std::lock(...) or something like that. But for that, you first need a wrapper class for GIL to make it a lockable object.

    Is there any case that may lead to a deadlock?

    If both of them (GIL release and mutex lock) taken together as proposed, I do not think a deadlock can happen.