Skip to content

Commit ecf287a

Browse files
committed
Allow per-interpreter internals/local_internals
1 parent 7d962b6 commit ecf287a

File tree

6 files changed

+170
-10
lines changed

6 files changed

+170
-10
lines changed

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ option(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION
9292
"To enforce that a handle_type_name<> specialization exists" OFF)
9393
option(PYBIND11_SIMPLE_GIL_MANAGEMENT
9494
"Use simpler GIL management logic that does not support disassociation" OFF)
95+
option(PYBIND11_SUBINTERPRETER_SUPPORT "Enable support for sub-interpreters" OFF)
9596
option(PYBIND11_NUMPY_1_ONLY
9697
"Disable NumPy 2 support to avoid changes to previous pybind11 versions." OFF)
9798
set(PYBIND11_INTERNALS_VERSION
@@ -105,6 +106,9 @@ endif()
105106
if(PYBIND11_SIMPLE_GIL_MANAGEMENT)
106107
add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT)
107108
endif()
109+
if(PYBIND11_SUBINTERPRETER_SUPPORT)
110+
add_compile_definitions(PYBIND11_SUBINTERPRETER_SUPPORT)
111+
endif()
108112
if(PYBIND11_NUMPY_1_ONLY)
109113
add_compile_definitions(PYBIND11_NUMPY_1_ONLY)
110114
endif()

include/pybind11/detail/internals.h

+79-5
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,25 @@ struct type_info {
260260
/// Each module locally stores a pointer to the `internals` data. The data
261261
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
262262
inline internals **&get_internals_pp() {
263+
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) || PY_VERSION_HEX < 0x030C0000 \
264+
|| !defined(PYBIND11_SUBINTERPRETER_SUPPORT)
263265
static internals **internals_pp = nullptr;
266+
#else
267+
static thread_local internals **internals_pp = nullptr;
268+
// This is one per interpreter, we cache it but if the thread changed
269+
// then we need to invalidate our cache
270+
// the caller will find the right value and set it if its null
271+
static thread_local PyThreadState *tstate_cached = nullptr;
272+
# if PY_VERSION_HEX < 0x030D0000
273+
PyThreadState *tstate = _PyThreadState_UncheckedGet();
274+
# else
275+
PyThreadState *tstate = PyThreadState_GetUnchecked();
276+
# endif
277+
if (tstate != tstate_cached) {
278+
tstate_cached = tstate;
279+
internals_pp = nullptr;
280+
}
281+
#endif
264282
return internals_pp;
265283
}
266284

@@ -427,9 +445,6 @@ PYBIND11_NOINLINE internals &get_internals() {
427445
return **internals_pp;
428446
}
429447

430-
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
431-
gil_scoped_acquire gil;
432-
#else
433448
// Ensure that the GIL is held since we will need to make Python calls.
434449
// Cannot use py::gil_scoped_acquire here since that constructor calls get_internals.
435450
struct gil_scoped_acquire_local {
@@ -439,7 +454,7 @@ PYBIND11_NOINLINE internals &get_internals() {
439454
~gil_scoped_acquire_local() { PyGILState_Release(state); }
440455
const PyGILState_STATE state;
441456
} gil;
442-
#endif
457+
443458
error_scope err_scope;
444459

445460
dict state_dict = get_python_state_dict();
@@ -455,7 +470,12 @@ PYBIND11_NOINLINE internals &get_internals() {
455470
// libc++ with CPython doesn't require this (types are explicitly exported)
456471
// libc++ with PyPy still need it, awaiting further investigation
457472
#if !defined(__GLIBCXX__)
458-
(*internals_pp)->registered_exception_translators.push_front(&translate_local_exception);
473+
if ((*internals_pp)->registered_exception_translators.empty()
474+
|| (*internals_pp)->registered_exception_translators.front()
475+
!= &translate_local_exception) {
476+
(*internals_pp)
477+
->registered_exception_translators.push_front(&translate_local_exception);
478+
}
459479
#endif
460480
} else {
461481
if (!internals_pp) {
@@ -515,7 +535,61 @@ inline local_internals &get_local_internals() {
515535
// static deinitialization fiasco. In order to avoid it we avoid destruction of the
516536
// local_internals static. One can read more about the problem and current solution here:
517537
// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
538+
539+
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON) || PY_VERSION_HEX < 0x030C0000 \
540+
|| !defined(PYBIND11_SUBINTERPRETER_SUPPORT)
518541
static auto *locals = new local_internals();
542+
#else
543+
static thread_local local_internals *locals = nullptr;
544+
// This is one per interpreter, we cache it but if the interpreter changed
545+
// then we need to invalidate our cache and re-fetch from the state dict
546+
static thread_local PyThreadState *tstate_cached = nullptr;
547+
# if PY_VERSION_HEX < 0x030D0000
548+
PyThreadState *tstate = _PyThreadState_UncheckedGet();
549+
# else
550+
PyThreadState *tstate = PyThreadState_GetUnchecked();
551+
# endif
552+
if (!tstate) {
553+
pybind11_fail(
554+
"pybind11::detail::get_local_internals() called without a current python thread");
555+
}
556+
if (tstate != tstate_cached) {
557+
// we create a unique value at first run which is based on a pointer to
558+
// a (non-thread_local) static value in this function, then multiple
559+
// loaded modules using this code will still each have a unique key.
560+
static const std::string this_module_idstr
561+
= PYBIND11_MODULE_LOCAL_ID
562+
+ std::to_string(reinterpret_cast<uintptr_t>(&this_module_idstr));
563+
564+
// Ensure that the GIL is held since we will need to make Python calls.
565+
// Cannot use py::gil_scoped_acquire here since that constructor calls get_internals.
566+
struct gil_scoped_acquire_local {
567+
gil_scoped_acquire_local() : state(PyGILState_Ensure()) {}
568+
gil_scoped_acquire_local(const gil_scoped_acquire_local &) = delete;
569+
gil_scoped_acquire_local &operator=(const gil_scoped_acquire_local &) = delete;
570+
~gil_scoped_acquire_local() { PyGILState_Release(state); }
571+
const PyGILState_STATE state;
572+
} gil;
573+
574+
error_scope err_scope;
575+
dict state_dict = get_python_state_dict();
576+
object local_capsule = reinterpret_steal<object>(
577+
dict_getitemstringref(state_dict.ptr(), this_module_idstr.c_str()));
578+
if (!local_capsule) {
579+
locals = new local_internals();
580+
state_dict[this_module_idstr.c_str()] = capsule(reinterpret_cast<void *>(locals));
581+
} else {
582+
void *ptr = PyCapsule_GetPointer(local_capsule.ptr(), nullptr);
583+
if (!ptr) {
584+
raise_from(PyExc_SystemError, "pybind11::detail::get_local_internals() FAILED");
585+
throw error_already_set();
586+
}
587+
locals = reinterpret_cast<local_internals *>(ptr);
588+
}
589+
tstate_cached = tstate;
590+
}
591+
#endif
592+
519593
return *locals;
520594
}
521595

include/pybind11/pybind11.h

+62-1
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,26 @@ class mod_gil_not_used {
12551255
bool flag_;
12561256
};
12571257

1258+
// Use to activate Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
1259+
class mod_per_interpreter_gil {
1260+
public:
1261+
explicit mod_per_interpreter_gil(bool flag = true) : flag_(flag) {}
1262+
bool flag() const { return flag_; }
1263+
1264+
private:
1265+
bool flag_;
1266+
};
1267+
1268+
// Use to activate Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED
1269+
class mod_multi_interpreter_one_gil {
1270+
public:
1271+
explicit mod_multi_interpreter_one_gil(bool flag = true) : flag_(flag) {}
1272+
bool flag() const { return flag_; }
1273+
1274+
private:
1275+
bool flag_;
1276+
};
1277+
12581278
PYBIND11_NAMESPACE_BEGIN(detail)
12591279

12601280
inline bool gil_not_used_option() { return false; }
@@ -1269,6 +1289,35 @@ inline bool gil_not_used_option(F &&, O &&...o) {
12691289
return false || gil_not_used_option(o...);
12701290
}
12711291

1292+
#ifdef Py_mod_multiple_interpreters
1293+
inline void *multi_interp_option() { return Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED; }
1294+
# ifdef PYBIND11_SUBINTERPRETER_SUPPORT
1295+
template <typename F, typename... O>
1296+
void *multi_interp_option(F &&, O &&...o);
1297+
template <typename... O>
1298+
void *multi_interp_option(mod_multi_interpreter_one_gil f, O &&...o);
1299+
template <typename... O>
1300+
inline void *multi_interp_option(mod_per_interpreter_gil f, O &&...o) {
1301+
if (f.flag()) {
1302+
return Py_MOD_PER_INTERPRETER_GIL_SUPPORTED;
1303+
}
1304+
return multi_interp_option(o...);
1305+
}
1306+
template <typename... O>
1307+
inline void *multi_interp_option(mod_multi_interpreter_one_gil f, O &&...o) {
1308+
void *others = multi_interp_option(o...);
1309+
if (!f.flag() || others == Py_MOD_PER_INTERPRETER_GIL_SUPPORTED) {
1310+
return others;
1311+
}
1312+
return Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED;
1313+
}
1314+
# endif
1315+
template <typename F, typename... O>
1316+
inline void *multi_interp_option(F &&, O &&...o) {
1317+
return multi_interp_option(o...);
1318+
}
1319+
#endif
1320+
12721321
PYBIND11_NAMESPACE_END(detail)
12731322

12741323
/// Wrapper for Python extension modules
@@ -1411,7 +1460,7 @@ class module_ : public object {
14111460
14121461
``def`` should point to a statically allocated module_def.
14131462
``slots`` must point to an initialized array of slots with space for at
1414-
least one additional slot to be populated based on the options.
1463+
least two additional slots to be populated based on the options.
14151464
\endrst */
14161465
template <typename... Options>
14171466
static object init_module_def(const char *name,
@@ -1426,6 +1475,18 @@ class module_ : public object {
14261475

14271476
bool nogil PYBIND11_MAYBE_UNUSED = detail::gil_not_used_option(options...);
14281477

1478+
#ifdef Py_mod_multiple_interpreters
1479+
slots[i].slot = Py_mod_multiple_interpreters;
1480+
slots[i].value = detail::multi_interp_option(options...);
1481+
if (nogil && slots[i].value == Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED) {
1482+
// if you support free threading and multi-interpreters,
1483+
// then you definitely also support per-interpreter GIL
1484+
// even if you don't know it.
1485+
slots[i].value = Py_MOD_PER_INTERPRETER_GIL_SUPPORTED;
1486+
}
1487+
++i;
1488+
#endif
1489+
14291490
if (nogil) {
14301491
#ifdef Py_mod_gil
14311492
slots[i].slot = Py_mod_gil;

tests/test_embed/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ endif()
2929
find_package(Threads REQUIRED)
3030

3131
add_executable(test_embed catch.cpp test_interpreter.cpp)
32+
target_compile_definitions(test_embed PRIVATE "PYBIND11_SUBINTERPRETER_SUPPORT")
3233
pybind11_enable_warnings(test_embed)
3334

3435
target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads)
@@ -44,6 +45,7 @@ add_custom_target(
4445
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
4546

4647
pybind11_add_module(external_module THIN_LTO external_module.cpp)
48+
target_compile_definitions(external_module PRIVATE "PYBIND11_SUBINTERPRETER_SUPPORT")
4749
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY
4850
"${CMAKE_CURRENT_BINARY_DIR}")
4951
foreach(config ${CMAKE_CONFIGURATION_TYPES})

tests/test_embed/external_module.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace py = pybind11;
66
* modules aren't preserved over a finalize/initialize.
77
*/
88

9-
PYBIND11_MODULE(external_module, m, py::mod_gil_not_used()) {
9+
PYBIND11_MODULE(external_module, m, py::mod_gil_not_used(), py::mod_multi_interpreter_one_gil()) {
1010
class A {
1111
public:
1212
explicit A(int value) : v{value} {};

tests/test_embed/test_interpreter.cpp

+22-3
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ TEST_CASE("Restart the interpreter") {
323323
}
324324

325325
TEST_CASE("Subinterpreter") {
326+
py::module_::import("external_module"); // in the main interpreter
327+
326328
// Add tags to the modules in the main interpreter and test the basics.
327329
py::module_::import("__main__").attr("main_tag") = "main interpreter";
328330
{
@@ -338,11 +340,24 @@ TEST_CASE("Subinterpreter") {
338340
auto *main_tstate = PyThreadState_Get();
339341
auto *sub_tstate = Py_NewInterpreter();
340342

341-
// Subinterpreters get their own copy of builtins. detail::get_internals() still
342-
// works by returning from the static variable, i.e. all interpreters share a single
343-
// global pybind11::internals;
343+
// Subinterpreters get their own copy of builtins.
344344
REQUIRE_FALSE(has_state_dict_internals_obj());
345+
346+
#if defined(PYBIND11_SUBINTERPRETER_SUPPORT) && PY_VERSION_HEX >= 0x030C0000
347+
// internals hasn't been populated yet, but will be different for the subinterpreter
348+
REQUIRE_FALSE(has_pybind11_internals_static());
349+
350+
py::list sys_path = py::module_::import("sys").attr("path");
351+
sys_path.append(py::str("."));
352+
353+
auto ext_int = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
354+
py::detail::get_internals();
345355
REQUIRE(has_pybind11_internals_static());
356+
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) == ext_int);
357+
#else
358+
// This static is still defined
359+
REQUIRE(has_pybind11_internals_static());
360+
#endif
346361

347362
// Modules tags should be gone.
348363
REQUIRE_FALSE(py::hasattr(py::module_::import("__main__"), "tag"));
@@ -354,12 +369,16 @@ TEST_CASE("Subinterpreter") {
354369
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
355370
}
356371

372+
// The subinterpreter now has internals populated since we imported a pybind11 module
373+
REQUIRE(has_pybind11_internals_static());
374+
357375
// Restore main interpreter.
358376
Py_EndInterpreter(sub_tstate);
359377
PyThreadState_Swap(main_tstate);
360378

361379
REQUIRE(py::hasattr(py::module_::import("__main__"), "main_tag"));
362380
REQUIRE(py::hasattr(py::module_::import("widget_module"), "extension_module_tag"));
381+
REQUIRE(has_state_dict_internals_obj());
363382
}
364383

365384
TEST_CASE("Execution frame") {

0 commit comments

Comments
 (0)