AssertionError (3.X only) when calling Py_Finalize with threads
Asked Answered
T

1

3

I'm getting an error output when I call the C-API's Py_Finalize() from a different C-thread than I made a python call on.

The error I'm seeing is:

Exception ignored in: <module 'threading' from 'C:\\Python34-32\\Lib\\threading.py'>
Traceback (most recent call last):
  File "C:\Python34-32\Lib\threading.py", line 1289, in _shutdown
    assert tlock.locked()
AssertionError:

This only happens in Python 3.X (tested with 3.4.2), in Python 2.7 the exact same code doesn't have any issues.

Here's a minimal example that shows it happening when a C-thread is used, but not when everything happens on a single c-thread:

#include <iostream>
#include <fstream>
#include <thread>
#include <cassert>

#include <Python.h>

void make_file()
{
   std::fstream file("my_test.py", std::ios::out);
   file << 
      "import threading\n"   << 
      "def my_function():\n" << 
      "    pass\n"             ;
   file.close();
}

void exec()
{
   PyGILState_STATE gstate = PyGILState_Ensure();
   PyObject* pdict = PyDict_New();
   PyDict_SetItemString(pdict, "__builtins__", PyEval_GetBuiltins());

   PyRun_String("import my_test", Py_file_input, pdict, pdict);
   PyRun_String("my_test.my_function()", Py_file_input, pdict, pdict);
   assert(!PyErr_Occurred());
   PyGILState_Release(gstate);
}

void basic()
{
   std::cout << "--Starting Basic--" << std::endl;

   Py_Initialize();
   PyEval_InitThreads();
   PyThreadState* threadState = PyEval_SaveThread();

   exec();

   PyEval_RestoreThread(threadState);
   Py_Finalize();

   std::cout << "--Basic Complete--" << std::endl;
}

void with_thread()
{
   std::cout << "--Starting With Thread--" << std::endl;

   Py_Initialize();
   PyEval_InitThreads();
   PyThreadState* threadState = PyEval_SaveThread();

   std::thread t(exec);
   t.join();

   PyEval_RestoreThread(threadState);
   Py_Finalize();

   std::cout << "--With Thread Complete--" << std::endl;
}

int main(int argc, char* argv[])
{
   make_file();
   basic();
   with_thread();

   return 0;
}

output

--Starting Basic--
--Basic Complete--
--Starting With Thread--
Exception ignored in: <module 'threading' from 'C:\\Python34-32\\Lib\\threading.py'>
Traceback (most recent call last):
  File "C:\Python34-32\Lib\threading.py", line 1289, in _shutdown
    assert tlock.locked()
AssertionError:
--With Thread Complete--

The order of the basic()/with_thread() calls in main does not matter, I can even include those lines multiple times with no affect, each with_thread() call results in the error output.

Edit :

Making the threadState global, then changing exec to:

void exec()
{
   //PyGILState_STATE gstate = PyGILState_Ensure();
   PyEval_RestoreThread(threadState); 
   PyObject* pdict = PyDict_New();
   PyDict_SetItemString(pdict, "__builtins__", PyEval_GetBuiltins());

   PyRun_String("import my_test", Py_file_input, pdict, pdict);
   PyRun_String("my_test.my_function()", Py_file_input, pdict, pdict);
   assert(!PyErr_Occurred());
   //PyGILState_Release(gstate);
   threadState = PyEval_SaveThread();
}

causes the error to go away, however then I have a global value I need to coordinate between the users of my library (in my actual code, the exec() function could be written by anybody and I have a lot more initialization stuff that I run). Any insights on how to make the GIL grabbing more isolated like the original example while keeping thread compatibility?

Tetrasyllable answered 8/1, 2015 at 16:17 Comment(2)
Did you ever get more info on this?Knute
No, I just ended up doing basically what I'm showing in the edit I made at the bottom. The only difference is I'm not actually using a global, I make a class that contains it which any users of the python thread need to pass back and forth.Tetrasyllable
C
7

Try by adding

Py_DECREF(PyImport_ImportModule("threading"));

after

PyEval_InitThreads();
Citronella answered 19/3, 2016 at 23:50 Comment(3)
That seems to work in the above example when used in either the single threadded or multi-threadded example. Why?!?Tetrasyllable
I had the same problem and this solved it. Does anyone know why this works??Joey
For pybind11: pybind11::scoped_interpreter interpreter; (void)pybind11::detail::get_internals(); Py_DECREF(PyImport_ImportModule("threading")); - the detail::get_internals() call does the PyEval_InitThreads().Goodygoody

© 2022 - 2024 — McMap. All rights reserved.