Skip to content

Commit 404b477

Browse files
committed
Introduce get_python_state_dict()
1 parent 442261d commit 404b477

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

include/pybind11/detail/internals.h

+40-7
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,39 @@ inline void translate_local_exception(std::exception_ptr p) {
421421
}
422422
#endif
423423

424+
inline object get_python_state_dict() {
425+
object state_dict;
426+
#if (PYBIND11_INTERNALS_VERSION <= 4 && PY_VERSION_HEX < 0x030C0000) \
427+
|| PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION)
428+
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
429+
#else
430+
# if PY_VERSION_HEX < 0x03090000
431+
PyInterpreterState *istate = _PyInterpreterState_Get();
432+
# else
433+
PyInterpreterState *istate = PyInterpreterState_Get();
434+
# endif
435+
if (istate) {
436+
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
437+
}
438+
#endif
439+
if (!state_dict) {
440+
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
441+
}
442+
return state_dict;
443+
}
444+
445+
inline object get_internals_obj_from_state_dict(handle state_dict) {
446+
return reinterpret_borrow<object>(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID));
447+
}
448+
449+
inline internals **get_internals_pp_from_capsule(handle obj) {
450+
void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr);
451+
if (raw_ptr == nullptr) {
452+
raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED");
453+
}
454+
return static_cast<internals **>(raw_ptr);
455+
}
456+
424457
/// Return a reference to the current `internals` data
425458
PYBIND11_NOINLINE internals &get_internals() {
426459
auto **&internals_pp = get_internals_pp();
@@ -445,12 +478,12 @@ PYBIND11_NOINLINE internals &get_internals() {
445478
#endif
446479
error_scope err_scope;
447480

448-
PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID);
449-
auto builtins = handle(PyEval_GetBuiltins());
450-
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
451-
internals_pp = static_cast<internals **>(capsule(builtins[id]));
452-
453-
// We loaded builtins through python's builtins, which means that our `error_already_set`
481+
dict state_dict = get_python_state_dict();
482+
if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) {
483+
internals_pp = get_internals_pp_from_capsule(internals_obj);
484+
}
485+
if (internals_pp && *internals_pp) {
486+
// We loaded the internals through `state_dict`, which means that our `error_already_set`
454487
// and `builtin_exception` may be different local classes than the ones set up in the
455488
// initial exception translator, below, so add another for our local exception classes.
456489
//
@@ -484,7 +517,7 @@ PYBIND11_NOINLINE internals &get_internals() {
484517
# endif
485518
internals_ptr->istate = tstate->interp;
486519
#endif
487-
builtins[id] = capsule(internals_pp);
520+
state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp);
488521
internals_ptr->registered_exception_translators.push_front(&translate_exception);
489522
internals_ptr->static_property_type = make_static_property_type();
490523
internals_ptr->default_metaclass = make_default_metaclass();

include/pybind11/embed.h

+4-6
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,14 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
243243
244244
\endrst */
245245
inline void finalize_interpreter() {
246-
handle builtins(PyEval_GetBuiltins());
247-
const char *id = PYBIND11_INTERNALS_ID;
248-
249246
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the
250247
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
251248
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
252249
detail::internals **internals_ptr_ptr = detail::get_internals_pp();
253-
// It could also be stashed in builtins, so look there too:
254-
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
255-
internals_ptr_ptr = capsule(builtins[id]);
250+
// It could also be stashed in state_dict, so look there too:
251+
if (object internals_obj
252+
= get_internals_obj_from_state_dict(detail::get_python_state_dict())) {
253+
internals_ptr_ptr = detail::get_internals_pp_from_capsule(internals_obj);
256254
}
257255
// Local internals contains data managed by the current interpreter, so we must clear them to
258256
// avoid undefined behaviors when initializing another interpreter

tests/test_embed/test_interpreter.cpp

+11-11
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,10 @@ TEST_CASE("Add program dir to path using PyConfig") {
255255
}
256256
#endif
257257

258-
bool has_pybind11_internals_builtin() {
259-
auto builtins = py::handle(PyEval_GetBuiltins());
260-
return builtins.contains(PYBIND11_INTERNALS_ID);
261-
};
258+
bool has_state_dict_internals_obj() {
259+
return bool(
260+
py::detail::get_internals_obj_from_state_dict(py::detail::get_python_state_dict()));
261+
}
262262

263263
bool has_pybind11_internals_static() {
264264
auto **&ipp = py::detail::get_internals_pp();
@@ -268,7 +268,7 @@ bool has_pybind11_internals_static() {
268268
TEST_CASE("Restart the interpreter") {
269269
// Verify pre-restart state.
270270
REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
271-
REQUIRE(has_pybind11_internals_builtin());
271+
REQUIRE(has_state_dict_internals_obj());
272272
REQUIRE(has_pybind11_internals_static());
273273
REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
274274
== 123);
@@ -285,10 +285,10 @@ TEST_CASE("Restart the interpreter") {
285285
REQUIRE(Py_IsInitialized() == 1);
286286

287287
// Internals are deleted after a restart.
288-
REQUIRE_FALSE(has_pybind11_internals_builtin());
288+
REQUIRE_FALSE(has_state_dict_internals_obj());
289289
REQUIRE_FALSE(has_pybind11_internals_static());
290290
pybind11::detail::get_internals();
291-
REQUIRE(has_pybind11_internals_builtin());
291+
REQUIRE(has_state_dict_internals_obj());
292292
REQUIRE(has_pybind11_internals_static());
293293
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp())
294294
== py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
@@ -303,13 +303,13 @@ TEST_CASE("Restart the interpreter") {
303303
py::detail::get_internals();
304304
*static_cast<bool *>(ran) = true;
305305
});
306-
REQUIRE_FALSE(has_pybind11_internals_builtin());
306+
REQUIRE_FALSE(has_state_dict_internals_obj());
307307
REQUIRE_FALSE(has_pybind11_internals_static());
308308
REQUIRE_FALSE(ran);
309309
py::finalize_interpreter();
310310
REQUIRE(ran);
311311
py::initialize_interpreter();
312-
REQUIRE_FALSE(has_pybind11_internals_builtin());
312+
REQUIRE_FALSE(has_state_dict_internals_obj());
313313
REQUIRE_FALSE(has_pybind11_internals_static());
314314

315315
// C++ modules can be reloaded.
@@ -331,7 +331,7 @@ TEST_CASE("Subinterpreter") {
331331

332332
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
333333
}
334-
REQUIRE(has_pybind11_internals_builtin());
334+
REQUIRE(has_state_dict_internals_obj());
335335
REQUIRE(has_pybind11_internals_static());
336336

337337
/// Create and switch to a subinterpreter.
@@ -341,7 +341,7 @@ TEST_CASE("Subinterpreter") {
341341
// Subinterpreters get their own copy of builtins. detail::get_internals() still
342342
// works by returning from the static variable, i.e. all interpreters share a single
343343
// global pybind11::internals;
344-
REQUIRE_FALSE(has_pybind11_internals_builtin());
344+
REQUIRE_FALSE(has_state_dict_internals_obj());
345345
REQUIRE(has_pybind11_internals_static());
346346

347347
// Modules tags should be gone.

0 commit comments

Comments
 (0)