Skip to content

Commit 4238def

Browse files
committed
Destroy internals if created during Py_Finalize()
Py_Finalize could potentially invoke code that calls `get_internals()`, which could create a new internals object if one didn't exist. `finalize_interpreter()` didn't catch this because it only used the pre-finalize interpreter pointer status; if this happens, it results in the internals pointer not being properly destroyed with the interpreter, which leaks, and also causes a `get_internals()` under a future interpreter to return an internals object that is wrong in various ways.
1 parent ef2dfd4 commit 4238def

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

include/pybind11/embed.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ inline void finalize_interpreter() {
143143
handle builtins(PyEval_GetBuiltins());
144144
const char *id = PYBIND11_INTERNALS_ID;
145145

146-
detail::internals **internals_ptr_ptr = nullptr;
146+
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the
147+
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
148+
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
149+
detail::internals **internals_ptr_ptr = &detail::get_internals_ptr();
150+
// It could also be stashed in builtins, so look there too:
147151
if (builtins.contains(id) && isinstance<capsule>(builtins[id]))
148152
internals_ptr_ptr = capsule(builtins[id]);
149153

tests/test_embed/test_interpreter.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,20 @@ TEST_CASE("There can be only one interpreter") {
8484
py::initialize_interpreter();
8585
}
8686

87-
bool has_pybind11_internals() {
87+
bool has_pybind11_internals_builtin() {
8888
auto builtins = py::handle(PyEval_GetBuiltins());
8989
return builtins.contains(PYBIND11_INTERNALS_ID);
9090
};
9191

92+
bool has_pybind11_internals_static() {
93+
return py::detail::get_internals_ptr() != nullptr;
94+
}
95+
9296
TEST_CASE("Restart the interpreter") {
9397
// Verify pre-restart state.
9498
REQUIRE(py::module::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
95-
REQUIRE(has_pybind11_internals());
99+
REQUIRE(has_pybind11_internals_builtin());
100+
REQUIRE(has_pybind11_internals_static());
96101

97102
// Restart the interpreter.
98103
py::finalize_interpreter();
@@ -102,9 +107,27 @@ TEST_CASE("Restart the interpreter") {
102107
REQUIRE(Py_IsInitialized() == 1);
103108

104109
// Internals are deleted after a restart.
105-
REQUIRE_FALSE(has_pybind11_internals());
110+
REQUIRE_FALSE(has_pybind11_internals_builtin());
111+
REQUIRE_FALSE(has_pybind11_internals_static());
106112
pybind11::detail::get_internals();
107-
REQUIRE(has_pybind11_internals());
113+
REQUIRE(has_pybind11_internals_builtin());
114+
REQUIRE(has_pybind11_internals_static());
115+
116+
// Make sure that an interpreter with no get_internals() created until finalize still gets the
117+
// internals destroyed
118+
py::finalize_interpreter();
119+
py::initialize_interpreter();
120+
bool ran = false;
121+
py::module::import("__main__").attr("internals_destroy_test") =
122+
py::capsule(&ran, [](void *ran) { py::detail::get_internals(); *static_cast<bool *>(ran) = true; });
123+
REQUIRE_FALSE(has_pybind11_internals_builtin());
124+
REQUIRE_FALSE(has_pybind11_internals_static());
125+
REQUIRE_FALSE(ran);
126+
py::finalize_interpreter();
127+
REQUIRE(ran);
128+
py::initialize_interpreter();
129+
REQUIRE_FALSE(has_pybind11_internals_builtin());
130+
REQUIRE_FALSE(has_pybind11_internals_static());
108131

109132
// C++ modules can be reloaded.
110133
auto cpp_module = py::module::import("widget_module");
@@ -125,7 +148,8 @@ TEST_CASE("Subinterpreter") {
125148

126149
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
127150
}
128-
REQUIRE(has_pybind11_internals());
151+
REQUIRE(has_pybind11_internals_builtin());
152+
REQUIRE(has_pybind11_internals_static());
129153

130154
/// Create and switch to a subinterpreter.
131155
auto main_tstate = PyThreadState_Get();
@@ -134,7 +158,8 @@ TEST_CASE("Subinterpreter") {
134158
// Subinterpreters get their own copy of builtins. detail::get_internals() still
135159
// works by returning from the static variable, i.e. all interpreters share a single
136160
// global pybind11::internals;
137-
REQUIRE_FALSE(has_pybind11_internals());
161+
REQUIRE_FALSE(has_pybind11_internals_builtin());
162+
REQUIRE(has_pybind11_internals_static());
138163

139164
// Modules tags should be gone.
140165
REQUIRE_FALSE(py::hasattr(py::module::import("__main__"), "tag"));

0 commit comments

Comments
 (0)