Skip to content

Commit c7026d0

Browse files
b-passpre-commit-ci[bot]henryiii
authored
fix!: modify the internals pointer-to-pointer implementation to not use thread_local (#5709)
* Refactor internals to use a holder that manages the PP * Refactor internals to use a holder that manages the PP * Fix cleanup/destruction issues. * Fix one more destruction issue Should now just be able to delete the internals PP on destruction * Make clang-tidy happy * Try to fix exception translators issue on certain platforms Also fix a couple more pedantic warings * Fix test, after internals is free'd it can come back at the same address So instead, just make sure it was zero'd and don't try to compare the addresses. Also a little code cleanup * Comment tweak [skip ci] * Switch to ifdef instead of if * Re-enable subinterpreters in iOS * style: pre-commit fixes * Oops, this snuck in on merge * fix: bump ABI version to 10 Signed-off-by: Henry Schreiner <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Henry Schreiner <[email protected]>
1 parent b194891 commit c7026d0

File tree

9 files changed

+265
-231
lines changed

9 files changed

+265
-231
lines changed

include/pybind11/detail/common.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@
256256

257257
// Slightly faster code paths are available when PYBIND11_HAS_SUBINTERPRETER_SUPPORT is *not*
258258
// defined, so avoid defining it for implementations that do not support subinterpreters. However,
259-
// defining it unnecessarily is not expected to break anything (other than old iOS targets).
259+
// defining it unnecessarily is not expected to break anything.
260260
// This can be overridden by the user with -DPYBIND11_HAS_SUBINTERPRETER_SUPPORT=1 or 0
261261
#ifndef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
262262
# if PY_VERSION_HEX >= 0x030C0000 && !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
@@ -345,7 +345,11 @@
345345
#define PYBIND11_STRINGIFY(x) #x
346346
#define PYBIND11_TOSTRING(x) PYBIND11_STRINGIFY(x)
347347
#define PYBIND11_CONCAT(first, second) first##second
348-
#define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals();
348+
#define PYBIND11_ENSURE_INTERNALS_READY \
349+
{ \
350+
pybind11::detail::get_internals_pp_manager().unref(); \
351+
pybind11::detail::get_internals(); \
352+
}
349353

350354
#if !defined(GRAALVM_PYTHON)
351355
# define PYBIND11_PYCFUNCTION_GET_DOC(func) ((func)->m_ml->ml_doc)

include/pybind11/detail/internals.h

Lines changed: 207 additions & 141 deletions
Large diffs are not rendered by default.

include/pybind11/detail/type_caster_base.h

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,21 @@ class loader_life_support {
4545
loader_life_support *parent = nullptr;
4646
std::unordered_set<PyObject *> keep_alive;
4747

48-
// Store stack pointer in thread-local storage.
49-
static PYBIND11_TLS_KEY_REF get_stack_tls_key() {
50-
return get_internals().loader_life_support_tls_key;
51-
}
52-
static loader_life_support *get_stack_top() {
53-
return static_cast<loader_life_support *>(PYBIND11_TLS_GET_VALUE(get_stack_tls_key()));
54-
}
55-
static void set_stack_top(loader_life_support *value) {
56-
PYBIND11_TLS_REPLACE_VALUE(get_stack_tls_key(), value);
57-
}
58-
5948
public:
6049
/// A new patient frame is created when a function is entered
61-
loader_life_support() : parent{get_stack_top()} { set_stack_top(this); }
50+
loader_life_support() {
51+
auto &stack_top = get_internals().loader_life_support_tls;
52+
parent = stack_top.get();
53+
stack_top = this;
54+
}
6255

6356
/// ... and destroyed after it returns
6457
~loader_life_support() {
65-
if (get_stack_top() != this) {
58+
auto &stack_top = get_internals().loader_life_support_tls;
59+
if (stack_top.get() != this) {
6660
pybind11_fail("loader_life_support: internal error");
6761
}
68-
set_stack_top(parent);
62+
stack_top = parent;
6963
for (auto *item : keep_alive) {
7064
Py_DECREF(item);
7165
}
@@ -74,7 +68,7 @@ class loader_life_support {
7468
/// This can only be used inside a pybind11-bound function, either by `argument_loader`
7569
/// at argument preparation time or by `py::cast()` at execution time.
7670
PYBIND11_NOINLINE static void add_patient(handle h) {
77-
loader_life_support *frame = get_stack_top();
71+
loader_life_support *frame = get_internals().loader_life_support_tls.get();
7872
if (!frame) {
7973
// NOTE: It would be nice to include the stack frames here, as this indicates
8074
// use of pybind11::cast<> outside the normal call framework, finding such

include/pybind11/embed.h

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -240,31 +240,29 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
240240
241241
\endrst */
242242
inline void finalize_interpreter() {
243-
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the
244-
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
245-
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
246-
auto *&internals_ptr_ptr = detail::get_internals_pp<detail::internals>();
247-
auto *&local_internals_ptr_ptr = detail::get_internals_pp<detail::local_internals>();
248-
{
249-
dict state_dict = detail::get_python_state_dict();
250-
internals_ptr_ptr = detail::get_internals_pp_from_capsule_in_state_dict<detail::internals>(
251-
state_dict, PYBIND11_INTERNALS_ID);
252-
local_internals_ptr_ptr
253-
= detail::get_internals_pp_from_capsule_in_state_dict<detail::local_internals>(
254-
state_dict, detail::get_local_internals_id());
243+
// get rid of any thread-local interpreter cache that currently exists
244+
if (detail::get_num_interpreters_seen() > 1) {
245+
detail::get_internals_pp_manager().unref();
246+
detail::get_local_internals_pp_manager().unref();
247+
248+
// We know there can be no other interpreter alive now, so we can lower the count
249+
detail::get_num_interpreters_seen() = 1;
255250
}
256251

252+
// Re-fetch the internals pointer-to-pointer (but not the internals itself, which might not
253+
// exist). It's possible for the internals to be created during Py_Finalize() (e.g. if a
254+
// py::capsule calls `get_internals()` during destruction), so we get the pointer-pointer here
255+
// and check it after Py_Finalize().
256+
detail::get_internals_pp_manager().get_pp();
257+
detail::get_local_internals_pp_manager().get_pp();
258+
257259
Py_Finalize();
258260

259-
if (internals_ptr_ptr) {
260-
internals_ptr_ptr->reset();
261-
}
261+
detail::get_internals_pp_manager().destroy();
262262

263263
// Local internals contains data managed by the current interpreter, so we must clear them to
264264
// avoid undefined behaviors when initializing another interpreter
265-
if (local_internals_ptr_ptr) {
266-
local_internals_ptr_ptr->reset();
267-
}
265+
detail::get_local_internals_pp_manager().destroy();
268266

269267
// We know there is no interpreter alive now, so we can reset the count
270268
detail::get_num_interpreters_seen() = 0;

include/pybind11/gil.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class gil_scoped_acquire {
6868
public:
6969
PYBIND11_NOINLINE gil_scoped_acquire() {
7070
auto &internals = detail::get_internals();
71-
tstate = (PyThreadState *) PYBIND11_TLS_GET_VALUE(internals.tstate);
71+
tstate = internals.tstate.get();
7272

7373
if (!tstate) {
7474
/* Check if the GIL was acquired using the PyGILState_* API instead (e.g. if
@@ -87,7 +87,7 @@ class gil_scoped_acquire {
8787
}
8888
# endif
8989
tstate->gilstate_counter = 0;
90-
PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate);
90+
internals.tstate = tstate;
9191
} else {
9292
release = detail::get_thread_state_unchecked() != tstate;
9393
}
@@ -124,7 +124,7 @@ class gil_scoped_acquire {
124124
if (active) {
125125
PyThreadState_DeleteCurrent();
126126
}
127-
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
127+
detail::get_internals().tstate.reset();
128128
release = false;
129129
}
130130
}
@@ -161,8 +161,7 @@ class gil_scoped_release {
161161
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
162162
tstate = PyEval_SaveThread();
163163
if (disassoc) {
164-
auto key = internals.tstate; // NOLINT(readability-qualified-auto)
165-
PYBIND11_TLS_DELETE_VALUE(key);
164+
internals.tstate.reset();
166165
}
167166
}
168167

@@ -185,8 +184,7 @@ class gil_scoped_release {
185184
PyEval_RestoreThread(tstate);
186185
}
187186
if (disassoc) {
188-
auto key = detail::get_internals().tstate; // NOLINT(readability-qualified-auto)
189-
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
187+
detail::get_internals().tstate = tstate;
190188
}
191189
}
192190

include/pybind11/subinterpreter.h

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -159,34 +159,19 @@ class subinterpreter {
159159

160160
bool switch_back = old_tstate && old_tstate->interp != istate_;
161161

162-
// Get the internals pointer (without creating it if it doesn't exist). It's possible
163-
// for the internals to be created during Py_EndInterpreter() (e.g. if a py::capsule
164-
// calls `get_internals()` during destruction), so we get the pointer-pointer here and
165-
// check it after.
166-
auto *&internals_ptr_ptr = detail::get_internals_pp<detail::internals>();
167-
auto *&local_internals_ptr_ptr = detail::get_internals_pp<detail::local_internals>();
168-
{
169-
dict sd = state_dict();
170-
internals_ptr_ptr
171-
= detail::get_internals_pp_from_capsule_in_state_dict<detail::internals>(
172-
sd, PYBIND11_INTERNALS_ID);
173-
local_internals_ptr_ptr
174-
= detail::get_internals_pp_from_capsule_in_state_dict<detail::local_internals>(
175-
sd, detail::get_local_internals_id());
176-
}
162+
// Internals always exists in the subinterpreter, this class enforces it when it creates
163+
// the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
164+
// internals themselves.
165+
detail::get_internals_pp_manager().get_pp();
166+
detail::get_local_internals_pp_manager().get_pp();
177167

178168
// End it
179169
Py_EndInterpreter(destroy_tstate);
180170

181-
// do NOT decrease detail::get_num_interpreters_seen, because it can never decrease
182-
// while other threads are running...
183-
184-
if (internals_ptr_ptr) {
185-
internals_ptr_ptr->reset();
186-
}
187-
if (local_internals_ptr_ptr) {
188-
local_internals_ptr_ptr->reset();
189-
}
171+
// It's possible for the internals to be created during endinterpreter (e.g. if a
172+
// py::capsule calls `get_internals()` during destruction), so we destroy afterward.
173+
detail::get_internals_pp_manager().destroy();
174+
detail::get_local_internals_pp_manager().destroy();
190175

191176
// switch back to the old tstate and old GIL (if there was one)
192177
if (switch_back)
@@ -271,7 +256,7 @@ inline subinterpreter_scoped_activate::subinterpreter_scoped_activate(subinterpr
271256
old_tstate_ = PyThreadState_Swap(tstate_);
272257

273258
// save this in internals for scoped_gil calls
274-
PYBIND11_TLS_REPLACE_VALUE(detail::get_internals().tstate, tstate_);
259+
detail::get_internals().tstate = tstate_;
275260
}
276261

277262
inline subinterpreter_scoped_activate::~subinterpreter_scoped_activate() {
@@ -307,7 +292,7 @@ inline subinterpreter_scoped_activate::~subinterpreter_scoped_activate() {
307292
pybind11_fail("~subinterpreter_scoped_activate: thread state must be current!");
308293
}
309294
#endif
310-
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
295+
detail::get_internals().tstate.reset();
311296
PyThreadState_Clear(tstate_);
312297
PyThreadState_DeleteCurrent();
313298
}

tests/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,3 @@ pyodide.test-groups = ["numpy", "scipy"]
3333
ios.test-groups = ["numpy"]
3434
ios.xbuild-tools = ["cmake", "ninja"]
3535
ios.environment.PIP_EXTRA_INDEX_URL = "https://pypi.anaconda.org/beeware/simple"
36-
ios.config-settings."cmake.define.CMAKE_CXX_FLAGS" = "-DPYBIND11_HAS_SUBINTERPRETER_SUPPORT=0"

tests/test_embed/test_interpreter.cpp

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,8 @@ bool has_state_dict_internals_obj() {
2525
return state.contains(PYBIND11_INTERNALS_ID);
2626
}
2727

28-
bool has_pybind11_internals_static() {
29-
auto *&ipp = py::detail::get_internals_pp<py::detail::internals>();
30-
return (ipp != nullptr) && *ipp;
31-
}
32-
3328
uintptr_t get_details_as_uintptr() {
34-
return reinterpret_cast<uintptr_t>(
35-
py::detail::get_internals_pp<py::detail::internals>()->get());
29+
return reinterpret_cast<uintptr_t>(py::detail::get_internals_pp_manager().get_pp()->get());
3630
}
3731

3832
class Widget {
@@ -278,7 +272,6 @@ TEST_CASE("Restart the interpreter") {
278272
// Verify pre-restart state.
279273
REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
280274
REQUIRE(has_state_dict_internals_obj());
281-
REQUIRE(has_pybind11_internals_static());
282275
REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
283276
== 123);
284277

@@ -295,10 +288,10 @@ TEST_CASE("Restart the interpreter") {
295288

296289
// Internals are deleted after a restart.
297290
REQUIRE_FALSE(has_state_dict_internals_obj());
298-
REQUIRE_FALSE(has_pybind11_internals_static());
291+
REQUIRE(get_details_as_uintptr() == 0);
299292
pybind11::detail::get_internals();
300293
REQUIRE(has_state_dict_internals_obj());
301-
REQUIRE(has_pybind11_internals_static());
294+
REQUIRE(get_details_as_uintptr() != 0);
302295
REQUIRE(get_details_as_uintptr()
303296
== py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
304297

@@ -311,18 +304,15 @@ TEST_CASE("Restart the interpreter") {
311304
= py::capsule(&ran, [](void *ran) {
312305
py::detail::get_internals();
313306
REQUIRE(has_state_dict_internals_obj());
314-
REQUIRE(has_pybind11_internals_static());
315307
*static_cast<bool *>(ran) = true;
316308
});
317309
REQUIRE_FALSE(has_state_dict_internals_obj());
318-
REQUIRE_FALSE(has_pybind11_internals_static());
319310
REQUIRE_FALSE(ran);
320311
py::finalize_interpreter();
321312
REQUIRE(ran);
322-
REQUIRE_FALSE(has_pybind11_internals_static());
323313
py::initialize_interpreter();
324314
REQUIRE_FALSE(has_state_dict_internals_obj());
325-
REQUIRE_FALSE(has_pybind11_internals_static());
315+
REQUIRE(get_details_as_uintptr() == 0);
326316

327317
// C++ modules can be reloaded.
328318
auto cpp_module = py::module_::import("widget_module");
@@ -348,7 +338,6 @@ TEST_CASE("Threads") {
348338
// Restart interpreter to ensure threads are not initialized
349339
py::finalize_interpreter();
350340
py::initialize_interpreter();
351-
REQUIRE_FALSE(has_pybind11_internals_static());
352341

353342
constexpr auto num_threads = 10;
354343
auto locals = py::dict("count"_a = 0);

tests/test_embed/test_subinterpreter.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,21 @@ namespace py = pybind11;
1717
using namespace py::literals;
1818

1919
bool has_state_dict_internals_obj();
20-
bool has_pybind11_internals_static();
2120
uintptr_t get_details_as_uintptr();
2221

2322
void unsafe_reset_internals_for_single_interpreter() {
2423
// unsafe normally, but for subsequent tests, put this back.. we know there are no threads
2524
// running and only 1 interpreter
25+
py::detail::get_internals_pp_manager().unref();
26+
py::detail::get_local_internals_pp_manager().unref();
2627
py::detail::get_num_interpreters_seen() = 1;
27-
py::detail::get_internals_pp<py::detail::internals>() = nullptr;
2828
py::detail::get_internals();
29-
py::detail::get_internals_pp<py::detail::local_internals>() = nullptr;
3029
py::detail::get_local_internals();
3130
}
3231

3332
TEST_CASE("Single Subinterpreter") {
33+
unsafe_reset_internals_for_single_interpreter();
34+
3435
py::module_::import("external_module"); // in the main interpreter
3536

3637
// Add tags to the modules in the main interpreter and test the basics.
@@ -42,7 +43,6 @@ TEST_CASE("Single Subinterpreter") {
4243
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
4344
}
4445
REQUIRE(has_state_dict_internals_obj());
45-
REQUIRE(has_pybind11_internals_static());
4646

4747
auto main_int
4848
= py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
@@ -52,14 +52,13 @@ TEST_CASE("Single Subinterpreter") {
5252
py::scoped_subinterpreter ssi;
5353

5454
// The subinterpreter has internals populated
55-
REQUIRE(has_pybind11_internals_static());
55+
REQUIRE(has_state_dict_internals_obj());
5656

5757
py::list(py::module_::import("sys").attr("path")).append(py::str("."));
5858

5959
auto ext_int
6060
= py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
6161
py::detail::get_internals();
62-
REQUIRE(has_pybind11_internals_static());
6362
REQUIRE(get_details_as_uintptr() == ext_int);
6463
REQUIRE(ext_int != main_int);
6564

@@ -205,6 +204,8 @@ TEST_CASE("GIL Subinterpreter") {
205204
}
206205

207206
TEST_CASE("Multiple Subinterpreters") {
207+
unsafe_reset_internals_for_single_interpreter();
208+
208209
// Make sure the module is in the main interpreter and save its pointer
209210
auto *main_ext = py::module_::import("external_module").ptr();
210211
auto main_int

0 commit comments

Comments
 (0)