From ee28a3049f81f530a8c4a75e3930643efb8d6114 Mon Sep 17 00:00:00 2001 From: b-pass Date: Mon, 5 Oct 2026 20:49:03 -0400 Subject: [PATCH 1/2] Move the unref to below the multi-interpreter check. --- include/pybind11/detail/common.h | 7 +++---- include/pybind11/detail/internals-inl.h | 7 ++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7445c01e45..5975c4e569 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -381,8 +381,7 @@ #define PYBIND11_CONCAT(first, second) first##second #define PYBIND11_ENSURE_INTERNALS_READY \ { \ - pybind11::detail::get_internals_pp_manager().unref(); \ - pybind11::detail::get_internals(); \ + pybind11::detail::ensure_internals(); \ } #if !defined(GRAALVM_PYTHON) @@ -471,7 +470,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across PYBIND11_CHECK_PYTHON_VERSION \ PYBIND11_PRECOMPILED_CONFIG_GUARD \ try { \ - pybind11::detail::ensure_internals(); \ + PYBIND11_ENSURE_INTERNALS_READY \ static ::pybind11::detail::slots_array mod_def_slots \ = ::pybind11::detail::init_slots( \ &PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \ @@ -494,7 +493,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \ int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \ try { \ - pybind11::detail::ensure_internals(); \ + PYBIND11_ENSURE_INTERNALS_READY \ auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \ if (!pybind11::detail::get_cached_module(m.attr("__spec__").attr("name"))) { \ PYBIND11_CONCAT(pybind11_init_, name)(m); \ diff --git a/include/pybind11/detail/internals-inl.h b/include/pybind11/detail/internals-inl.h index ea1998435a..7d93347c5f 100644 --- a/include/pybind11/detail/internals-inl.h +++ b/include/pybind11/detail/internals-inl.h @@ -207,12 +207,17 @@ PYBIND11_INLINE PyObject *get_local_internals_capsule() { } PYBIND11_INLINE void ensure_internals() { - pybind11::detail::get_internals_pp_manager().unref(); #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT if (PyInterpreterState_Get() != PyInterpreterState_Main()) { has_seen_non_main_interpreter() = true; } #endif + /* In an embedded app the main interpreter could be finalized and re-initialized and a pybind11 + * extension module would hold a pointer to a deleted internals. The only way to prevent + * access to that is to re-fetch everything from the state dict here. So we first null it out + * of our global copy and then fetch it (creating it if it does not already exist). */ + pybind11::detail::get_internals_pp_manager().unref(); + pybind11::detail::get_local_internals_pp_manager().unref(); pybind11::detail::get_internals(); } From ad8e65a33cc43aef5495c7e523ce3c38912cf6c8 Mon Sep 17 00:00:00 2001 From: b-pass Date: Tue, 6 Oct 2026 21:48:17 -0400 Subject: [PATCH 2/2] Remove ensure_internals from early module init. The internals are not needed here, and this is always the main interpreter (which is sometimes NOT the interpreter we want). --- include/pybind11/detail/common.h | 1 - include/pybind11/pybind11.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 5975c4e569..6f68461bab 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -470,7 +470,6 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across PYBIND11_CHECK_PYTHON_VERSION \ PYBIND11_PRECOMPILED_CONFIG_GUARD \ try { \ - PYBIND11_ENSURE_INTERNALS_READY \ static ::pybind11::detail::slots_array mod_def_slots \ = ::pybind11::detail::init_slots( \ &PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \ diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 9cb8aee5b6..b2346c545f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -678,7 +678,8 @@ PyObject *cached_create_module(PyObject *spec, PyModuleDef *); /// the sentinel (0) end slot. using slots_array = std::array; -/// Initialize an array of slots based on the supplied exec slot and options. +/** Initialize an array of slots based on the supplied exec slot and options. This code may not + * assume the GIL and may not use pybind11 internals because this runs early during module init. */ template inline slots_array init_slots(int (*exec_fn)(PyObject *), Options &&...options) noexcept { /* NOTE: slots_array MUST be large enough to hold all possible options. If you add an option