Skip to content

Commit baa540e

Browse files
colesburyhenryiiipre-commit-ci[bot]rwgk
authored
fix: support free-threaded CPython with GIL disabled (#5148)
* Support free-threaded CPython (PEP 703) Some additional locking is added in the free-threaded build when `Py_GIL_DISABLED` is defined: - Most accesses to internals are protected by a single mutex - The registered_instances uses a striped lock to improve concurrency Pybind11 modules can indicate support for running with the GIL disabled by calling `set_gil_not_used()`. * refactor: use PYBIND11_MODULE (#11) Signed-off-by: Henry Schreiner <[email protected]> * Address code review * Suppress MSVC warning * Changes from review * style: pre-commit fixes * `py::mod_gil_not_used()` suggestion. * Update include/pybind11/pybind11.h --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]>
1 parent 1961b96 commit baa540e

15 files changed

+356
-141
lines changed

include/pybind11/detail/class.h

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -205,39 +205,40 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
205205

206206
/// Cleanup the type-info for a pybind11-registered type.
207207
extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
208-
auto *type = (PyTypeObject *) obj;
209-
auto &internals = get_internals();
210-
211-
// A pybind11-registered type will:
212-
// 1) be found in internals.registered_types_py
213-
// 2) have exactly one associated `detail::type_info`
214-
auto found_type = internals.registered_types_py.find(type);
215-
if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1
216-
&& found_type->second[0]->type == type) {
217-
218-
auto *tinfo = found_type->second[0];
219-
auto tindex = std::type_index(*tinfo->cpptype);
220-
internals.direct_conversions.erase(tindex);
221-
222-
if (tinfo->module_local) {
223-
get_local_internals().registered_types_cpp.erase(tindex);
224-
} else {
225-
internals.registered_types_cpp.erase(tindex);
226-
}
227-
internals.registered_types_py.erase(tinfo->type);
228-
229-
// Actually just `std::erase_if`, but that's only available in C++20
230-
auto &cache = internals.inactive_override_cache;
231-
for (auto it = cache.begin(), last = cache.end(); it != last;) {
232-
if (it->first == (PyObject *) tinfo->type) {
233-
it = cache.erase(it);
208+
with_internals([obj](internals &internals) {
209+
auto *type = (PyTypeObject *) obj;
210+
211+
// A pybind11-registered type will:
212+
// 1) be found in internals.registered_types_py
213+
// 2) have exactly one associated `detail::type_info`
214+
auto found_type = internals.registered_types_py.find(type);
215+
if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1
216+
&& found_type->second[0]->type == type) {
217+
218+
auto *tinfo = found_type->second[0];
219+
auto tindex = std::type_index(*tinfo->cpptype);
220+
internals.direct_conversions.erase(tindex);
221+
222+
if (tinfo->module_local) {
223+
get_local_internals().registered_types_cpp.erase(tindex);
234224
} else {
235-
++it;
225+
internals.registered_types_cpp.erase(tindex);
226+
}
227+
internals.registered_types_py.erase(tinfo->type);
228+
229+
// Actually just `std::erase_if`, but that's only available in C++20
230+
auto &cache = internals.inactive_override_cache;
231+
for (auto it = cache.begin(), last = cache.end(); it != last;) {
232+
if (it->first == (PyObject *) tinfo->type) {
233+
it = cache.erase(it);
234+
} else {
235+
++it;
236+
}
236237
}
237-
}
238238

239-
delete tinfo;
240-
}
239+
delete tinfo;
240+
}
241+
});
241242

242243
PyType_Type.tp_dealloc(obj);
243244
}
@@ -310,19 +311,20 @@ inline void traverse_offset_bases(void *valueptr,
310311
}
311312

312313
inline bool register_instance_impl(void *ptr, instance *self) {
313-
get_internals().registered_instances.emplace(ptr, self);
314+
with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); });
314315
return true; // unused, but gives the same signature as the deregister func
315316
}
316317
inline bool deregister_instance_impl(void *ptr, instance *self) {
317-
auto &registered_instances = get_internals().registered_instances;
318-
auto range = registered_instances.equal_range(ptr);
319-
for (auto it = range.first; it != range.second; ++it) {
320-
if (self == it->second) {
321-
registered_instances.erase(it);
322-
return true;
318+
return with_instance_map(ptr, [&](instance_map &instances) {
319+
auto range = instances.equal_range(ptr);
320+
for (auto it = range.first; it != range.second; ++it) {
321+
if (self == it->second) {
322+
instances.erase(it);
323+
return true;
324+
}
323325
}
324-
}
325-
return false;
326+
return false;
327+
});
326328
}
327329

328330
inline void register_instance(instance *self, void *valptr, const type_info *tinfo) {
@@ -377,27 +379,32 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject
377379
}
378380

379381
inline void add_patient(PyObject *nurse, PyObject *patient) {
380-
auto &internals = get_internals();
381382
auto *instance = reinterpret_cast<detail::instance *>(nurse);
382383
instance->has_patients = true;
383384
Py_INCREF(patient);
384-
internals.patients[nurse].push_back(patient);
385+
386+
with_internals([&](internals &internals) { internals.patients[nurse].push_back(patient); });
385387
}
386388

387389
inline void clear_patients(PyObject *self) {
388390
auto *instance = reinterpret_cast<detail::instance *>(self);
389-
auto &internals = get_internals();
390-
auto pos = internals.patients.find(self);
391+
std::vector<PyObject *> patients;
391392

392-
if (pos == internals.patients.end()) {
393-
pybind11_fail("FATAL: Internal consistency check failed: Invalid clear_patients() call.");
394-
}
393+
with_internals([&](internals &internals) {
394+
auto pos = internals.patients.find(self);
395+
396+
if (pos == internals.patients.end()) {
397+
pybind11_fail(
398+
"FATAL: Internal consistency check failed: Invalid clear_patients() call.");
399+
}
400+
401+
// Clearing the patients can cause more Python code to run, which
402+
// can invalidate the iterator. Extract the vector of patients
403+
// from the unordered_map first.
404+
patients = std::move(pos->second);
405+
internals.patients.erase(pos);
406+
});
395407

396-
// Clearing the patients can cause more Python code to run, which
397-
// can invalidate the iterator. Extract the vector of patients
398-
// from the unordered_map first.
399-
auto patients = std::move(pos->second);
400-
internals.patients.erase(pos);
401408
instance->has_patients = false;
402409
for (PyObject *&patient : patients) {
403410
Py_CLEAR(patient);
@@ -662,10 +669,13 @@ inline PyObject *make_new_python_type(const type_record &rec) {
662669

663670
char *tp_doc = nullptr;
664671
if (rec.doc && options::show_user_defined_docstrings()) {
665-
/* Allocate memory for docstring (using PyObject_MALLOC, since
666-
Python will free this later on) */
672+
/* Allocate memory for docstring (Python will free this later on) */
667673
size_t size = std::strlen(rec.doc) + 1;
674+
#if PY_VERSION_HEX >= 0x030D0000
675+
tp_doc = (char *) PyMem_MALLOC(size);
676+
#else
668677
tp_doc = (char *) PyObject_MALLOC(size);
678+
#endif
669679
std::memcpy((void *) tp_doc, rec.doc, size);
670680
}
671681

include/pybind11/detail/common.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ PYBIND11_WARNING_POP
464464
});
465465
}
466466
\endrst */
467-
#define PYBIND11_MODULE(name, variable) \
467+
#define PYBIND11_MODULE(name, variable, ...) \
468468
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name) \
469469
PYBIND11_MAYBE_UNUSED; \
470470
PYBIND11_MAYBE_UNUSED \
@@ -473,7 +473,10 @@ PYBIND11_WARNING_POP
473473
PYBIND11_CHECK_PYTHON_VERSION \
474474
PYBIND11_ENSURE_INTERNALS_READY \
475475
auto m = ::pybind11::module_::create_extension_module( \
476-
PYBIND11_TOSTRING(name), nullptr, &PYBIND11_CONCAT(pybind11_module_def_, name)); \
476+
PYBIND11_TOSTRING(name), \
477+
nullptr, \
478+
&PYBIND11_CONCAT(pybind11_module_def_, name), \
479+
##__VA_ARGS__); \
477480
try { \
478481
PYBIND11_CONCAT(pybind11_init_, name)(m); \
479482
return m.ptr(); \

0 commit comments

Comments
 (0)