fix: don't destruct module objects in atexit (#5688)

This commit is contained in:
b-pass
2025-05-25 18:56:26 -04:00
committed by GitHub
parent 1dd85ef42a
commit 03b4a9e56f
3 changed files with 61 additions and 94 deletions

View File

@@ -385,35 +385,39 @@
} \
PyObject *pybind11_init()
// this push is for the next several macros
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
/**
Create a PyInit_ function for this module.
Note that this is run once for each (sub-)interpreter the module is imported into, including
possibly concurrently. The PyModuleDef is allowed to be static, but the PyObject* resulting from
PyModuleDef_Init should be treated like any other PyObject (so not shared across interpreters).
*/
#define PYBIND11_MODULE_PYINIT(name, pre_init, ...) \
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name); \
static ::pybind11::module_::slots_array PYBIND11_CONCAT(pybind11_module_slots_, name); \
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
PYBIND11_PLUGIN_IMPL(name) { \
PYBIND11_CHECK_PYTHON_VERSION \
pre_init; \
PYBIND11_ENSURE_INTERNALS_READY \
static auto result = []() { \
auto &slots = PYBIND11_CONCAT(pybind11_module_slots_, name); \
slots[0] = {Py_mod_exec, \
reinterpret_cast<void *>(&PYBIND11_CONCAT(pybind11_exec_, name))}; \
slots[1] = {0, nullptr}; \
return ::pybind11::module_::initialize_multiphase_module_def( \
PYBIND11_TOSTRING(name), \
nullptr, \
&PYBIND11_CONCAT(pybind11_module_def_, name), \
slots, \
##__VA_ARGS__); \
}(); \
return result.ptr(); \
static ::pybind11::detail::slots_array slots = ::pybind11::detail::init_slots( \
&PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
/* m_name */ PYBIND11_TOSTRING(name), \
/* m_doc */ nullptr, \
/* m_size */ 0, \
/* m_methods */ nullptr, \
/* m_slots */ slots.data(), \
/* m_traverse */ nullptr, \
/* m_clear */ nullptr, \
/* m_free */ nullptr}; \
return PyModuleDef_Init(&def); \
}
PYBIND11_WARNING_POP
#define PYBIND11_MODULE_EXEC(name, variable) \
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \
try { \
auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \
@@ -464,12 +468,12 @@ PYBIND11_WARNING_POP
}
\endrst */
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
#define PYBIND11_MODULE(name, variable, ...) \
PYBIND11_MODULE_PYINIT( \
name, (pybind11::detail::get_num_interpreters_seen() += 1), ##__VA_ARGS__) \
PYBIND11_MODULE_EXEC(name, variable)
// pop gnu-zero-variadic-macro-arguments
PYBIND11_WARNING_POP
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)

View File

@@ -1325,6 +1325,38 @@ inline void *multi_interp_slot(F &&, O &&...o) {
}
#endif
/// Must be a POD type, and must hold enough entries for all of the possible slots PLUS ONE for
/// the sentinel (0) end slot.
using slots_array = std::array<PyModuleDef_Slot, 4>;
/// Initialize an array of slots based on the supplied exec slot and options.
template <typename... Options>
static 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
here, you MUST also increase the size of slots_array in the type alias above! */
slots_array slots;
size_t next_slot = 0;
if (exec_fn != nullptr) {
slots[next_slot++] = {Py_mod_exec, reinterpret_cast<void *>(exec_fn)};
}
#ifdef Py_mod_multiple_interpreters
slots[next_slot++] = {Py_mod_multiple_interpreters, multi_interp_slot(options...)};
#endif
if (gil_not_used_option(options...)) {
#if defined(Py_mod_gil) && defined(Py_GIL_DISABLED)
slots[next_slot++] = {Py_mod_gil, Py_MOD_GIL_NOT_USED};
#endif
}
// slots must have a zero end sentinel
slots[next_slot++] = {0, nullptr};
return slots;
}
PYBIND11_NAMESPACE_END(detail)
/// Wrapper for Python extension modules
@@ -1438,19 +1470,19 @@ public:
PyModule_AddObject(ptr(), name, obj.inc_ref().ptr() /* steals a reference */);
}
using module_def = PyModuleDef; // TODO: Can this be removed (it was needed only for Python 2)?
// DEPRECATED (since PR #5688): Use PyModuleDef directly instead.
using module_def = PyModuleDef;
/** \rst
Create a new top-level module that can be used as the main module of a C extension.
``def`` should point to a statically allocated module_def.
``def`` should point to a statically allocated PyModuleDef.
\endrst */
static module_ create_extension_module(const char *name,
const char *doc,
module_def *def,
PyModuleDef *def,
mod_gil_not_used gil_not_used
= mod_gil_not_used(false)) {
// module_def is PyModuleDef
// Placement new (not an allocation).
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ name,
@@ -1478,74 +1510,6 @@ public:
// For Python 2, reinterpret_borrow was correct.
return reinterpret_borrow<module_>(m);
}
/// Must be a POD type, and must hold enough entries for all of the possible slots PLUS ONE for
/// the sentinel (0) end slot.
using slots_array = std::array<PyModuleDef_Slot, 4>;
/** \rst
Initialize a module def for use with multi-phase module initialization.
``def`` should point to a statically allocated module_def.
``slots`` must already contain a Py_mod_exec or Py_mod_create slot and will be filled with
additional slots from the supplied options (and the empty sentinel slot).
\endrst */
template <typename... Options>
static object initialize_multiphase_module_def(const char *name,
const char *doc,
module_def *def,
slots_array &slots,
Options &&...options) {
size_t next_slot = 0;
size_t term_slot = slots.size() - 1;
// find the end of the supplied slots
while (next_slot < term_slot && slots[next_slot].slot != 0) {
++next_slot;
}
#ifdef Py_mod_multiple_interpreters
if (next_slot >= term_slot) {
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
}
slots[next_slot++] = {Py_mod_multiple_interpreters, detail::multi_interp_slot(options...)};
#endif
if (detail::gil_not_used_option(options...)) {
#if defined(Py_mod_gil) && defined(Py_GIL_DISABLED)
if (next_slot >= term_slot) {
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
}
slots[next_slot++] = {Py_mod_gil, Py_MOD_GIL_NOT_USED};
#endif
}
// slots must have a zero end sentinel
if (next_slot > term_slot) {
pybind11_fail("initialize_multiphase_module_def: not enough space in slots");
}
slots[next_slot++] = {0, nullptr};
// module_def is PyModuleDef
// Placement new (not an allocation).
new (def) PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT,
/* m_name */ name,
/* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr,
/* m_size */ 0,
/* m_methods */ nullptr,
/* m_slots */ &slots[0],
/* m_traverse */ nullptr,
/* m_clear */ nullptr,
/* m_free */ nullptr};
auto *m = PyModuleDef_Init(def);
if (m == nullptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
pybind11_fail("Internal error in module_::initialize_multiphase_module_def()");
}
return reinterpret_borrow<object>(m);
}
};
PYBIND11_NAMESPACE_BEGIN(detail)

View File

@@ -78,8 +78,7 @@ TEST_SUBMODULE(modules, m) {
class DupeException {};
// Go ahead and leak, until we have a non-leaking py::module_ constructor
auto dm
= py::module_::create_extension_module("dummy", nullptr, new py::module_::module_def);
auto dm = py::module_::create_extension_module("dummy", nullptr, new PyModuleDef);
auto failures = py::list();
py::class_<Dupe1>(dm, "Dupe1");