Skip to content

Commit 2012b3c

Browse files
[cppyy] fix usage of Python GIL to support threading
1 parent 6d7f915 commit 2012b3c

File tree

2 files changed

+5
-28
lines changed

2 files changed

+5
-28
lines changed

bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& m
4141
// possible crash
4242
code << " PyObject* iself = (PyObject*)_internal_self;\n"
4343
" if (!iself || iself == Py_None) {\n"
44+
" PyGILState_STATE state = PyGILState_Ensure();\n"
4445
" PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n"
46+
" PyGILState_Release(state);\n"
4547
" return";
4648
if (retType != "void") {
4749
if (retType.back() != '*')
@@ -238,6 +240,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct,
238240
// object goes before the C++ one, only __del__ is called)
239241
if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) {
240242
code << " virtual ~" << derivedName << "() {\n"
243+
"PyGILState_STATE state = PyGILState_Ensure();\n"
241244
" PyObject* iself = (PyObject*)_internal_self;\n"
242245
" if (!iself || iself == Py_None)\n"
243246
" return;\n" // safe, as destructor always returns void
@@ -250,6 +253,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct,
250253
// magic C++ exception ...
251254
code << " if (!pyresult) PyErr_Print();\n"
252255
" else { Py_DECREF(pyresult); }\n"
256+
" PyGILState_Release(state);\n"
253257
" }\n";
254258
} else
255259
code << " virtual ~" << derivedName << "() {}\n";

bindings/pyroot/cppyy/cppyy/test/test_concurrent.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -110,41 +110,19 @@ def test04_cpp_threading_with_exceptions(self):
110110
};
111111
112112
struct worker {
113-
worker(consumer* c) : cons(c) {
114-
// Get the main interpreter state state to spawn new thread states
115-
PyThreadState* state = PyThreadState_Get();
116-
interpreterState = state->interp;
117-
}
113+
worker(consumer* c) : cons(c) {}
118114
~worker() { wait(); }
119115
120116
void start() {
121117
t = std::thread([this] {
122118
int counter = 0;
123-
124-
// Each thread needs a Python state object
125-
// Instead of using the higher-level PyGILState_Ensure and
126-
// PyGILState_Release functions, use the PyThreadState API
127-
// directly so that we only need to create one single
128-
// PyThreadState that can be restored and released in the
129-
// "hot loop".
130-
PyThreadState *pystate = PyThreadState_New(this->interpreterState);
131-
132119
while (counter++ < 10)
133120
try {
134-
PyEval_RestoreThread(pystate);
135121
cons->process(counter);
136-
PyEval_SaveThread();
137122
} catch (CPyCppyy::PyException& e) {
138123
err_msg = e.what();
139-
PyEval_SaveThread();
140124
return;
141125
}
142-
143-
PyEval_RestoreThread(pystate);
144-
PyThreadState_Clear(pystate);
145-
PyEval_SaveThread();
146-
147-
PyThreadState_Delete(pystate);
148126
});
149127
}
150128
@@ -153,7 +131,6 @@ def test04_cpp_threading_with_exceptions(self):
153131
t.join();
154132
}
155133
156-
PyInterpreterState* interpreterState = nullptr;
157134
std::thread t;
158135
consumer* cons = nullptr;
159136
std::string err_msg;
@@ -229,11 +206,7 @@ def test05_float2d_callback(self):
229206
for (int i = 0; i < channels; ++i)
230207
data[i] = new float[samples];
231208
232-
// Set Python thread because we call back into Python
233-
PyGILState_STATE gstate;
234-
gstate = PyGILState_Ensure();
235209
p.process(data, channels, samples);
236-
PyGILState_Release(gstate);
237210
238211
for (int i = 0; i < channels; ++i)
239212
delete[] data[i];

0 commit comments

Comments
 (0)