Skip to content

Commit 654fe92

Browse files
authored
Introduce get_python_state_dict() for Python 3.12 compatibility. (#4570)
* Introduce `get_python_state_dict()` * Conditional version bump for Python 3.12+ * Shuffle subexpressions to make the condition easier to understand (no change to logic). * Make pybind11 ABI version 5 the minimum for Python 3.12+ (as suggested by @lalaland) * Add back condition for PYPY_VERSION, but keep it open for future PyPy versions. * Fall back to simple `|| defined(PYPY_VERSION)`. `PY_VERSION_HEX` does not appear to be meaningful with PyPy.
1 parent 1e8b52a commit 654fe92

File tree

3 files changed

+64
-25
lines changed

3 files changed

+64
-25
lines changed

include/pybind11/detail/internals.h

+49-8
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,18 @@
3434
/// further ABI-incompatible changes may be made before the ABI is officially
3535
/// changed to the new version.
3636
#ifndef PYBIND11_INTERNALS_VERSION
37-
# define PYBIND11_INTERNALS_VERSION 4
37+
# if PY_VERSION_HEX >= 0x030C0000
38+
// Version bump for Python 3.12+, before first 3.12 beta release.
39+
# define PYBIND11_INTERNALS_VERSION 5
40+
# else
41+
# define PYBIND11_INTERNALS_VERSION 4
42+
# endif
3843
#endif
3944

45+
// This requirement is mainly to reduce the support burden (see PR #4570).
46+
static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5,
47+
"pybind11 ABI version 5 is the minimum for Python 3.12+");
48+
4049
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4150

4251
using ExceptionTranslator = void (*)(std::exception_ptr);
@@ -421,6 +430,38 @@ inline void translate_local_exception(std::exception_ptr p) {
421430
}
422431
#endif
423432

433+
inline object get_python_state_dict() {
434+
object state_dict;
435+
#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION)
436+
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
437+
#else
438+
# if PY_VERSION_HEX < 0x03090000
439+
PyInterpreterState *istate = _PyInterpreterState_Get();
440+
# else
441+
PyInterpreterState *istate = PyInterpreterState_Get();
442+
# endif
443+
if (istate) {
444+
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
445+
}
446+
#endif
447+
if (!state_dict) {
448+
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
449+
}
450+
return state_dict;
451+
}
452+
453+
inline object get_internals_obj_from_state_dict(handle state_dict) {
454+
return reinterpret_borrow<object>(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID));
455+
}
456+
457+
inline internals **get_internals_pp_from_capsule(handle obj) {
458+
void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr);
459+
if (raw_ptr == nullptr) {
460+
raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED");
461+
}
462+
return static_cast<internals **>(raw_ptr);
463+
}
464+
424465
/// Return a reference to the current `internals` data
425466
PYBIND11_NOINLINE internals &get_internals() {
426467
auto **&internals_pp = get_internals_pp();
@@ -445,12 +486,12 @@ PYBIND11_NOINLINE internals &get_internals() {
445486
#endif
446487
error_scope err_scope;
447488

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`
489+
dict state_dict = get_python_state_dict();
490+
if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) {
491+
internals_pp = get_internals_pp_from_capsule(internals_obj);
492+
}
493+
if (internals_pp && *internals_pp) {
494+
// We loaded the internals through `state_dict`, which means that our `error_already_set`
454495
// and `builtin_exception` may be different local classes than the ones set up in the
455496
// initial exception translator, below, so add another for our local exception classes.
456497
//
@@ -484,7 +525,7 @@ PYBIND11_NOINLINE internals &get_internals() {
484525
# endif
485526
internals_ptr->istate = tstate->interp;
486527
#endif
487-
builtins[id] = capsule(internals_pp);
528+
state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp);
488529
internals_ptr->registered_exception_translators.push_front(&translate_exception);
489530
internals_ptr->static_property_type = make_static_property_type();
490531
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)