Skip to content

Commit cdefbf3

Browse files
committed
Reapply to leak internals
This reverts commit 8f25a25.
1 parent 97e12d2 commit cdefbf3

File tree

4 files changed

+26
-88
lines changed

4 files changed

+26
-88
lines changed

include/pybind11/detail/internals.h

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -337,20 +337,7 @@ struct internals {
337337
internals(internals &&other) = delete;
338338
internals &operator=(const internals &other) = delete;
339339
internals &operator=(internals &&other) = delete;
340-
~internals() {
341-
// Normally this destructor runs during interpreter finalization and it may DECREF things.
342-
// In odd finalization scenarios it might end up running after the interpreter has
343-
// completely shut down, In that case, we should not decref these objects because pymalloc
344-
// is gone. This also applies across sub-interpreters, we should only DECREF when the
345-
// original owning interpreter is active.
346-
auto *cur_istate = get_interpreter_state_unchecked();
347-
if (cur_istate && cur_istate == istate) {
348-
gil_scoped_acquire_simple gil;
349-
Py_CLEAR(instance_base);
350-
Py_CLEAR(default_metaclass);
351-
Py_CLEAR(static_property_type);
352-
}
353-
}
340+
~internals() = default;
354341
};
355342

356343
// the internals struct (above) is shared between all the modules. local_internals are only
@@ -360,29 +347,13 @@ struct internals {
360347
// impact any other modules, because the only things accessing the local internals is the
361348
// module that contains them.
362349
struct local_internals {
363-
local_internals() : istate(get_interpreter_state_unchecked()) {}
364-
365350
// It should be safe to use fast_type_map here because this entire
366351
// data structure is scoped to our single module, and thus a single
367352
// DSO and single instance of type_info for any particular type.
368353
fast_type_map<type_info *> registered_types_cpp;
369354

370355
std::forward_list<ExceptionTranslator> registered_exception_translators;
371356
PyTypeObject *function_record_py_type = nullptr;
372-
PyInterpreterState *istate = nullptr;
373-
374-
~local_internals() {
375-
// Normally this destructor runs during interpreter finalization and it may DECREF things.
376-
// In odd finalization scenarios it might end up running after the interpreter has
377-
// completely shut down, In that case, we should not decref these objects because pymalloc
378-
// is gone. This also applies across sub-interpreters, we should only DECREF when the
379-
// original owning interpreter is active.
380-
auto *cur_istate = get_interpreter_state_unchecked();
381-
if (cur_istate && cur_istate == istate) {
382-
gil_scoped_acquire_simple gil;
383-
Py_CLEAR(function_record_py_type);
384-
}
385-
}
386357
};
387358

388359
enum class holder_enum_t : uint8_t {
@@ -580,7 +551,7 @@ inline void translate_local_exception(std::exception_ptr p) {
580551

581552
// Sentinel value for the `dtor` parameter of `atomic_get_or_create_in_state_dict`.
582553
// Indicates no destructor was explicitly provided (distinct from nullptr, which means "leak").
583-
#define PYBIND11_DTOR_UNSPECIFIED (reinterpret_cast<void (*)(PyObject *)>(1))
554+
#define PYBIND11_DTOR_USE_DELETE (reinterpret_cast<void (*)(PyObject *)>(1))
584555

585556
// Get or create per-storage capsule in the current interpreter's state dict.
586557
// - The storage is interpreter-dependent: different interpreters will have different storage.
@@ -605,7 +576,7 @@ inline void translate_local_exception(std::exception_ptr p) {
605576
template <typename Payload>
606577
std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
607578
void (*dtor)(PyObject *)
608-
= PYBIND11_DTOR_UNSPECIFIED) {
579+
= PYBIND11_DTOR_USE_DELETE) {
609580
error_scope err_scope; // preserve any existing Python error states
610581

611582
auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
@@ -651,7 +622,7 @@ std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
651622
// - Otherwise, our `new_capsule` is now in the dict, and it owns the storage and the state
652623
// dict will incref it. We need to set the caller's destructor on it, which will be
653624
// called when the interpreter shuts down.
654-
if (created && dtor != PYBIND11_DTOR_UNSPECIFIED) {
625+
if (created && dtor != PYBIND11_DTOR_USE_DELETE) {
655626
if (PyCapsule_SetDestructor(capsule_obj, dtor) < 0) {
656627
throw error_already_set();
657628
}
@@ -668,7 +639,7 @@ std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
668639
return std::pair<Payload *, bool>(static_cast<Payload *>(raw_ptr), created);
669640
}
670641

671-
#undef PYBIND11_DTOR_UNSPECIFIED
642+
#undef PYBIND11_DTOR_USE_DELETE
672643

673644
template <typename InternalsType>
674645
class internals_pp_manager {
@@ -741,27 +712,16 @@ class internals_pp_manager {
741712
internals_pp_manager(char const *id, on_fetch_function *on_fetch)
742713
: holder_id_(id), on_fetch_(on_fetch) {}
743714

744-
static void internals_shutdown(PyObject *capsule) {
745-
auto *pp = static_cast<std::unique_ptr<InternalsType> *>(
746-
PyCapsule_GetPointer(capsule, nullptr));
747-
if (pp) {
748-
pp->reset();
749-
}
750-
// We reset the unique_ptr's contents but cannot delete the unique_ptr itself here.
751-
// The pp_manager in this module (and possibly other modules sharing internals) holds
752-
// a raw pointer to this unique_ptr, and that pointer would dangle if we deleted it now.
753-
//
754-
// For pybind11-owned interpreters (via embed.h or subinterpreter.h), destroy() is
755-
// called after Py_Finalize/Py_EndInterpreter completes, which safely deletes the
756-
// unique_ptr. For interpreters not owned by pybind11 (e.g., a pybind11 extension
757-
// loaded into an external interpreter), destroy() is never called and the unique_ptr
758-
// shell (8 bytes, not its contents) is leaked.
759-
// (See PR #5958 for ideas to eliminate this leak.)
760-
}
761-
762715
std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
716+
// The `unique_ptr<InternalsType>` is intentionally leaked on interpreter shutdown.
717+
// Once an instance is created, it will never be deleted until the process exits (compare
718+
// to interpreter shutdown in multiple-interpreter scenarios).
719+
// We cannot guarantee the destruction order of capsules in the interpreter state dict on
720+
// interpreter shutdown, so deleting internals too early could cause undefined behavior
721+
// when other pybind11 objects access `get_internals()` during finalization (which would
722+
// recreate empty internals).
763723
auto result = atomic_get_or_create_in_state_dict<std::unique_ptr<InternalsType>>(
764-
holder_id_, &internals_shutdown);
724+
holder_id_, /*dtor=*/nullptr /* leak the capsule content */);
765725
auto *pp = result.first;
766726
bool created = result.second;
767727
// Only call on_fetch_ when fetching existing internals, not when creating new ones.
@@ -841,8 +801,6 @@ PYBIND11_NOINLINE internals &get_internals() {
841801

842802
/// Return the PyObject* for the internals capsule (borrowed reference).
843803
/// Returns nullptr if the capsule doesn't exist yet.
844-
/// This is used to prevent use-after-free during interpreter shutdown by allowing pybind11 types
845-
/// to hold a reference to the capsule (see comments in generic_type::initialize).
846804
inline PyObject *get_internals_capsule() {
847805
auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
848806
return dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID);
@@ -860,8 +818,6 @@ inline const std::string &get_local_internals_key() {
860818

861819
/// Return the PyObject* for the local_internals capsule (borrowed reference).
862820
/// Returns nullptr if the capsule doesn't exist yet.
863-
/// This is used to prevent use-after-free during interpreter shutdown by allowing pybind11 types
864-
/// to hold a reference to the capsule (see comments in generic_type::initialize).
865821
inline PyObject *get_local_internals_capsule() {
866822
const auto &key = get_local_internals_key();
867823
auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());

include/pybind11/pybind11.h

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,31 +1733,6 @@ class generic_type : public object {
17331733
PYBIND11_WARNING_POP
17341734
});
17351735

1736-
// Prevent use-after-free during interpreter shutdown. GC order is not guaranteed, so the
1737-
// internals capsule may be destroyed (resetting internals via internals_shutdown) before
1738-
// all pybind11 types are destroyed. If a type's tp_traverse/tp_clear then calls py::cast,
1739-
// it would recreate an empty internals and fail because the type registry is gone.
1740-
//
1741-
// By holding references to the capsules, we ensure they outlive all pybind11 types. We use
1742-
// weakrefs on the type with a cpp_function callback. When the type is destroyed, Python
1743-
// will call the callback which releases the capsule reference and the weakref.
1744-
if (PyObject *capsule = get_internals_capsule()) {
1745-
Py_INCREF(capsule);
1746-
(void) weakref(handle(m_ptr), cpp_function([](handle wr) -> void {
1747-
Py_XDECREF(get_internals_capsule());
1748-
wr.dec_ref();
1749-
}))
1750-
.release();
1751-
}
1752-
if (PyObject *capsule = get_local_internals_capsule()) {
1753-
Py_INCREF(capsule);
1754-
(void) weakref(handle(m_ptr), cpp_function([](handle wr) -> void {
1755-
Py_XDECREF(get_local_internals_capsule());
1756-
wr.dec_ref();
1757-
}))
1758-
.release();
1759-
}
1760-
17611736
if (rec.bases.size() > 1 || rec.multiple_inheritance) {
17621737
mark_parents_nonsimple(tinfo->type);
17631738
tinfo->simple_ancestors = false;

tests/test_custom_type_setup.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ void add_gc_checkers_with_weakrefs(const py::object &obj) {
3636
if (!global_capsule) {
3737
throw std::runtime_error("No global internals capsule found");
3838
}
39-
(void) py::weakref(obj, py::cpp_function([global_capsule, obj](py::handle weakref) -> void {
40-
py::handle new_global_capsule = py::detail::get_internals_capsule();
41-
if (!new_global_capsule.is(global_capsule)) {
39+
(void) py::weakref(obj, py::cpp_function([global_capsule](py::handle weakref) -> void {
40+
py::handle current_global_capsule = py::detail::get_internals_capsule();
41+
if (!current_global_capsule.is(global_capsule)) {
4242
throw std::runtime_error(
4343
"Global internals capsule was destroyed prematurely");
4444
}
@@ -51,9 +51,9 @@ void add_gc_checkers_with_weakrefs(const py::object &obj) {
5151
throw std::runtime_error("No local internals capsule found");
5252
}
5353
(void) py::weakref(
54-
obj, py::cpp_function([local_capsule, obj](py::handle weakref) -> void {
55-
py::handle new_local_capsule = py::detail::get_local_internals_capsule();
56-
if (!new_local_capsule.is(local_capsule)) {
54+
obj, py::cpp_function([local_capsule](py::handle weakref) -> void {
55+
py::handle current_local_capsule = py::detail::get_local_internals_capsule();
56+
if (!current_local_capsule.is(local_capsule)) {
5757
throw std::runtime_error("Local internals capsule was destroyed prematurely");
5858
}
5959
weakref.dec_ref();

tests/test_custom_type_setup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def test_indirect_cycle(gc_tester):
5858
)
5959
@pytest.mark.skipif("env.PYPY or env.GRAALPY")
6060
def test_py_cast_useable_on_shutdown():
61+
"""Test that py::cast works during interpreter shutdown."""
6162
env.check_script_success_in_subprocess(
6263
f"""
6364
import sys
@@ -67,8 +68,14 @@ def test_py_cast_useable_on_shutdown():
6768
6869
from pybind11_tests import custom_type_setup as m
6970
71+
# Create a self-referential cycle that will be collected during shutdown.
72+
# The tp_traverse and tp_clear callbacks call py::cast, which requires
73+
# internals to still be valid.
7074
obj = m.ContainerOwnsPythonObjects()
7175
obj.append(obj)
76+
77+
# Add weakref callbacks that verify the capsule is still alive when the
78+
# pybind11 object is garbage collected during shutdown.
7279
m.add_gc_checkers_with_weakrefs(obj)
7380
"""
7481
)

0 commit comments

Comments
 (0)