From 3bec467a1c4e2821eac1a1985d4d0ea796045274 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 3 Oct 2025 12:27:35 -0400 Subject: [PATCH] Fix thread-safety in `get_local_type_info()` Fixes potential thread-safety issues if types are concurrently registered while `get_local_type_info()` is called in free threaded Python. Use the `internals` mutex to also protect `local_internals`. This keeps the locking strategy simpler, and we already follow this pattern in some places, such as `pybind11_meta_dealloc`. --- .../detail/function_record_pyobject.h | 1 + include/pybind11/detail/type_caster_base.h | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/include/pybind11/detail/function_record_pyobject.h b/include/pybind11/detail/function_record_pyobject.h index d9c5bad938..694625f89b 100644 --- a/include/pybind11/detail/function_record_pyobject.h +++ b/include/pybind11/detail/function_record_pyobject.h @@ -91,6 +91,7 @@ static PyType_Spec function_record_PyType_Spec function_record_PyType_Slots}; inline PyTypeObject *get_function_record_PyTypeObject() { + PYBIND11_LOCK_INTERNALS(get_internals()); PyTypeObject *&py_type_obj = detail::get_local_internals().function_record_py_type; if (!py_type_obj) { PyObject *py_obj = PyType_FromSpec(&function_record_PyType_Spec); diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index a2512a5527..a07ef1a897 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -205,7 +205,7 @@ PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) { return bases.front(); } -inline detail::type_info *get_local_type_info(const std::type_info &tp) { +inline detail::type_info *get_local_type_info_lock_held(const std::type_info &tp) { const auto &locals = get_local_internals().registered_types_cpp; auto it = locals.find(&tp); if (it != locals.end()) { @@ -214,48 +214,59 @@ inline detail::type_info *get_local_type_info(const std::type_info &tp) { return nullptr; } -inline detail::type_info *get_global_type_info(const std::type_info &tp) { +inline detail::type_info *get_local_type_info(const std::type_info &tp) { + // NB: internals and local_internals share a single mutex + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_local_type_info_lock_held(tp); +} + +inline detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) { // This is a two-level lookup. Hopefully we find the type info in // registered_types_cpp_fast, but if not we try // registered_types_cpp and fill registered_types_cpp_fast for // next time. - return with_internals([&](internals &internals) { - detail::type_info *type_info = nullptr; + detail::type_info *type_info = nullptr; + auto &internals = get_internals(); #if PYBIND11_INTERNALS_VERSION >= 12 - auto &fast_types = internals.registered_types_cpp_fast; + auto &fast_types = internals.registered_types_cpp_fast; #endif - auto &types = internals.registered_types_cpp; + auto &types = internals.registered_types_cpp; #if PYBIND11_INTERNALS_VERSION >= 12 - auto fast_it = fast_types.find(&tp); - if (fast_it != fast_types.end()) { + auto fast_it = fast_types.find(&tp); + if (fast_it != fast_types.end()) { # ifndef NDEBUG - auto types_it = types.find(std::type_index(tp)); - assert(types_it != types.end()); - assert(types_it->second == fast_it->second); + auto types_it = types.find(std::type_index(tp)); + assert(types_it != types.end()); + assert(types_it->second == fast_it->second); # endif - return fast_it->second; - } + return fast_it->second; + } #endif // PYBIND11_INTERNALS_VERSION >= 12 - auto it = types.find(std::type_index(tp)); - if (it != types.end()) { + auto it = types.find(std::type_index(tp)); + if (it != types.end()) { #if PYBIND11_INTERNALS_VERSION >= 12 - fast_types.emplace(&tp, it->second); + fast_types.emplace(&tp, it->second); #endif - type_info = it->second; - } - return type_info; - }); + type_info = it->second; + } + return type_info; +} + +inline detail::type_info *get_global_type_info(const std::type_info &tp) { + PYBIND11_LOCK_INTERNALS(get_internals()); + return get_global_type_info_lock_held(tp); } /// Return the type info for a given C++ type; on lookup failure can either throw or return /// nullptr. PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp, bool throw_if_missing = false) { - if (auto *ltype = get_local_type_info(tp)) { + PYBIND11_LOCK_INTERNALS(get_internals()); + if (auto *ltype = get_local_type_info_lock_held(tp)) { return ltype; } - if (auto *gtype = get_global_type_info(tp)) { + if (auto *gtype = get_global_type_info_lock_held(tp)) { return gtype; }