mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-26 07:02:11 +00:00
Use PyGILState_GetThisThreadState
when using gil_scoped_acquire. (#1211)
This avoids GIL deadlocking when pybind11 tries to acquire the GIL in a thread that already acquired it using standard Python API (e.g. when running from a Python thread).
This commit is contained in:
parent
81da9888c7
commit
e2b884c33b
@ -1871,6 +1871,15 @@ public:
|
|||||||
auto const &internals = detail::get_internals();
|
auto const &internals = detail::get_internals();
|
||||||
tstate = (PyThreadState *) PYBIND11_TLS_GET_VALUE(internals.tstate);
|
tstate = (PyThreadState *) PYBIND11_TLS_GET_VALUE(internals.tstate);
|
||||||
|
|
||||||
|
if (!tstate) {
|
||||||
|
/* Check if the GIL was acquired using the PyGILState_* API instead (e.g. if
|
||||||
|
calling from a Python thread). Since we use a different key, this ensures
|
||||||
|
we don't create a new thread state and deadlock in PyEval_AcquireThread
|
||||||
|
below. Note we don't save this state with internals.tstate, since we don't
|
||||||
|
create it we would fail to clear it (its reference count should be > 0). */
|
||||||
|
tstate = PyGILState_GetThisThreadState();
|
||||||
|
}
|
||||||
|
|
||||||
if (!tstate) {
|
if (!tstate) {
|
||||||
tstate = PyThreadState_New(internals.istate);
|
tstate = PyThreadState_New(internals.istate);
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
|
@ -40,6 +40,7 @@ set(PYBIND11_TEST_FILES
|
|||||||
test_eval.cpp
|
test_eval.cpp
|
||||||
test_exceptions.cpp
|
test_exceptions.cpp
|
||||||
test_factory_constructors.cpp
|
test_factory_constructors.cpp
|
||||||
|
test_gil_scoped.cpp
|
||||||
test_iostream.cpp
|
test_iostream.cpp
|
||||||
test_kwargs_and_defaults.cpp
|
test_kwargs_and_defaults.cpp
|
||||||
test_local_bindings.cpp
|
test_local_bindings.cpp
|
||||||
|
43
tests/test_gil_scoped.cpp
Normal file
43
tests/test_gil_scoped.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
tests/test_gil_scoped.cpp -- acquire and release gil
|
||||||
|
|
||||||
|
Copyright (c) 2017 Borja Zarco (Google LLC) <bzarco@google.com>
|
||||||
|
|
||||||
|
All rights reserved. Use of this source code is governed by a
|
||||||
|
BSD-style license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pybind11_tests.h"
|
||||||
|
#include <pybind11/functional.h>
|
||||||
|
|
||||||
|
|
||||||
|
class VirtClass {
|
||||||
|
public:
|
||||||
|
virtual void virtual_func() {}
|
||||||
|
virtual void pure_virtual_func() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PyVirtClass : public VirtClass {
|
||||||
|
void virtual_func() override {
|
||||||
|
PYBIND11_OVERLOAD(void, VirtClass, virtual_func,);
|
||||||
|
}
|
||||||
|
void pure_virtual_func() override {
|
||||||
|
PYBIND11_OVERLOAD_PURE(void, VirtClass, pure_virtual_func,);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_SUBMODULE(gil_scoped, m) {
|
||||||
|
py::class_<VirtClass, PyVirtClass>(m, "VirtClass")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("virtual_func", &VirtClass::virtual_func)
|
||||||
|
.def("pure_virtual_func", &VirtClass::pure_virtual_func);
|
||||||
|
|
||||||
|
m.def("test_callback_py_obj",
|
||||||
|
[](py::object func) { func(); });
|
||||||
|
m.def("test_callback_std_func",
|
||||||
|
[](const std::function<void()> &func) { func(); });
|
||||||
|
m.def("test_callback_virtual_func",
|
||||||
|
[](VirtClass &virt) { virt.virtual_func(); });
|
||||||
|
m.def("test_callback_pure_virtual_func",
|
||||||
|
[](VirtClass &virt) { virt.pure_virtual_func(); });
|
||||||
|
}
|
80
tests/test_gil_scoped.py
Normal file
80
tests/test_gil_scoped.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import multiprocessing
|
||||||
|
import threading
|
||||||
|
from pybind11_tests import gil_scoped as m
|
||||||
|
|
||||||
|
|
||||||
|
def _run_in_process(target, *args, **kwargs):
|
||||||
|
"""Runs target in process and returns its exitcode after 1s (None if still alive)."""
|
||||||
|
process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
|
||||||
|
process.daemon = True
|
||||||
|
try:
|
||||||
|
process.start()
|
||||||
|
# Do not need to wait much, 1s should be more than enough.
|
||||||
|
process.join(timeout=1)
|
||||||
|
return process.exitcode
|
||||||
|
finally:
|
||||||
|
if process.is_alive():
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def _python_to_cpp_to_python():
|
||||||
|
"""Calls different C++ functions that come back to Python."""
|
||||||
|
class ExtendedVirtClass(m.VirtClass):
|
||||||
|
def virtual_func(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pure_virtual_func(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
extended = ExtendedVirtClass()
|
||||||
|
m.test_callback_py_obj(lambda: None)
|
||||||
|
m.test_callback_std_func(lambda: None)
|
||||||
|
m.test_callback_virtual_func(extended)
|
||||||
|
m.test_callback_pure_virtual_func(extended)
|
||||||
|
|
||||||
|
|
||||||
|
def _python_to_cpp_to_python_from_threads(num_threads, parallel=False):
|
||||||
|
"""Calls different C++ functions that come back to Python, from Python threads."""
|
||||||
|
threads = []
|
||||||
|
for _ in range(num_threads):
|
||||||
|
thread = threading.Thread(target=_python_to_cpp_to_python)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
if parallel:
|
||||||
|
threads.append(thread)
|
||||||
|
else:
|
||||||
|
thread.join()
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
def test_python_to_cpp_to_python_from_thread():
|
||||||
|
"""Makes sure there is no GIL deadlock when running in a thread.
|
||||||
|
|
||||||
|
It runs in a separate process to be able to stop and assert if it deadlocks.
|
||||||
|
"""
|
||||||
|
assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_python_to_cpp_to_python_from_thread_multiple_parallel():
|
||||||
|
"""Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
|
||||||
|
|
||||||
|
It runs in a separate process to be able to stop and assert if it deadlocks.
|
||||||
|
"""
|
||||||
|
assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_python_to_cpp_to_python_from_thread_multiple_sequential():
|
||||||
|
"""Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
|
||||||
|
|
||||||
|
It runs in a separate process to be able to stop and assert if it deadlocks.
|
||||||
|
"""
|
||||||
|
assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_python_to_cpp_to_python_from_process():
|
||||||
|
"""Makes sure there is no GIL deadlock when using processes.
|
||||||
|
|
||||||
|
This test is for completion, but it was never an issue.
|
||||||
|
"""
|
||||||
|
assert _run_in_process(_python_to_cpp_to_python) == 0
|
Loading…
Reference in New Issue
Block a user