Skip to content

Commit de4ba92

Browse files
authored
Add error_scope to detail::get_internals() (#3981)
* Add `error_scope` to `detail::get_internals()` * Adjust test to tolerate macOS PyPy behavior.
1 parent 8da58da commit de4ba92

File tree

5 files changed

+69
-0
lines changed

5 files changed

+69
-0
lines changed

include/pybind11/detail/internals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ PYBIND11_NOINLINE internals &get_internals() {
415415
~gil_scoped_acquire_local() { PyGILState_Release(state); }
416416
const PyGILState_STATE state;
417417
} gil;
418+
error_scope err_scope;
418419

419420
PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID);
420421
auto builtins = handle(PyEval_GetBuiltins());

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_
215215
"pybind11_cross_module_tests")
216216

217217
# And add additional targets for other tests.
218+
tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set")
218219
tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils")
219220

220221
set(PYBIND11_EIGEN_REPO
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright (c) 2022 Google LLC
3+
4+
All rights reserved. Use of this source code is governed by a
5+
BSD-style license that can be found in the LICENSE file.
6+
*/
7+
8+
#include <pybind11/pybind11.h>
9+
10+
// This file mimics a DSO that makes pybind11 calls but does not define a PYBIND11_MODULE,
11+
// so that the first call of cross_module_error_already_set() triggers the first call of
12+
// pybind11::detail::get_internals().
13+
14+
namespace {
15+
16+
namespace py = pybind11;
17+
18+
void interleaved_error_already_set() {
19+
PyErr_SetString(PyExc_RuntimeError, "1st error.");
20+
try {
21+
throw py::error_already_set();
22+
} catch (const py::error_already_set &) {
23+
// The 2nd error could be conditional in a real application.
24+
PyErr_SetString(PyExc_RuntimeError, "2nd error.");
25+
} // Here the 1st error is destroyed before the 2nd error is fetched.
26+
// The error_already_set dtor triggers a pybind11::detail::get_internals()
27+
// call via pybind11::gil_scoped_acquire.
28+
if (PyErr_Occurred()) {
29+
throw py::error_already_set();
30+
}
31+
}
32+
33+
constexpr char kModuleName[] = "cross_module_interleaved_error_already_set";
34+
35+
struct PyModuleDef moduledef = {
36+
PyModuleDef_HEAD_INIT, kModuleName, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr};
37+
38+
} // namespace
39+
40+
extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_interleaved_error_already_set() {
41+
PyObject *m = PyModule_Create(&moduledef);
42+
if (m != nullptr) {
43+
static_assert(sizeof(&interleaved_error_already_set) == sizeof(void *),
44+
"Function pointer must have the same size as void *");
45+
PyModule_AddObject(
46+
m,
47+
"funcaddr",
48+
PyLong_FromVoidPtr(reinterpret_cast<void *>(&interleaved_error_already_set)));
49+
}
50+
return m;
51+
}

tests/test_exceptions.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,11 @@ TEST_SUBMODULE(exceptions, m) {
307307
PyErr_Clear();
308308
return py::make_tuple(std::move(what), py_err_set_after_what);
309309
});
310+
311+
m.def("test_cross_module_interleaved_error_already_set", []() {
312+
auto cm = py::module_::import("cross_module_interleaved_error_already_set");
313+
auto interleaved_error_already_set
314+
= reinterpret_cast<void (*)()>(PyLong_AsVoidPtr(cm.attr("funcaddr").ptr()));
315+
interleaved_error_already_set();
316+
});
310317
}

tests/test_exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,12 @@ def test_flaky_exception_failure_point_str():
320320
with pytest.raises(ValueError) as excinfo:
321321
m.error_already_set_what(FlakyException, ("failure_point_str",))
322322
assert str(excinfo.value) == "triggered_failure_point_str"
323+
324+
325+
def test_cross_module_interleaved_error_already_set():
326+
with pytest.raises(RuntimeError) as excinfo:
327+
m.test_cross_module_interleaved_error_already_set()
328+
assert str(excinfo.value) in (
329+
"2nd error.", # Almost all platforms.
330+
"RuntimeError: 2nd error.", # Some PyPy builds (seen under macOS).
331+
)

0 commit comments

Comments
 (0)