diff --git a/include/boost/python/detail/pymutex.hpp b/include/boost/python/detail/pymutex.hpp new file mode 100644 index 00000000..2d2e2d62 --- /dev/null +++ b/include/boost/python/detail/pymutex.hpp @@ -0,0 +1,103 @@ +// Copyright 2025 Boost.Python Contributors +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PYTHON_DETAIL_PYMUTEX_HPP +#define BOOST_PYTHON_DETAIL_PYMUTEX_HPP + +#include +#ifdef Py_GIL_DISABLED +// needed for pymutex wrapper +#include +#include +#endif + +namespace boost { namespace python { namespace detail { + +#ifdef Py_GIL_DISABLED + +// Re-entrant wrapper around PyMutex for free-threaded Python +// Similar to _PyRecursiveMutex or threading.RLock +class pymutex { + PyMutex m_mutex; + std::atomic m_owner; + std::size_t m_level; + +public: + pymutex() : m_mutex({}), m_owner(0), m_level(0) {} + + // Non-copyable, non-movable + pymutex(const pymutex&) = delete; + pymutex& operator=(const pymutex&) = delete; + + void lock() { + unsigned long thread = PyThread_get_thread_ident(); + if (m_owner.load(std::memory_order_relaxed) == thread) { + m_level++; + return; + } + PyMutex_Lock(&m_mutex); + m_owner.store(thread, std::memory_order_relaxed); + // m_level should be 0 when we acquire the lock + } + + void unlock() { + unsigned long thread = PyThread_get_thread_ident(); + // Verify current thread owns the lock + if (m_owner.load(std::memory_order_relaxed) != thread) { + // This should never happen - programming error + return; + } + if (m_level > 0) { + m_level--; + return; + } + m_owner.store(0, std::memory_order_relaxed); + PyMutex_Unlock(&m_mutex); + } + + bool is_locked_by_current_thread() const { + unsigned long thread = PyThread_get_thread_ident(); + return m_owner.load(std::memory_order_relaxed) == thread; + } +}; + + +// RAII lock guard for pymutex +class pymutex_guard { + pymutex& m_mutex; + +public: + explicit pymutex_guard(pymutex& mutex) : m_mutex(mutex) { + m_mutex.lock(); + } + + ~pymutex_guard() { + m_mutex.unlock(); + } + + // Non-copyable, non-movable + pymutex_guard(const pymutex_guard&) = delete; + pymutex_guard& operator=(const pymutex_guard&) = delete; +}; + +// Global mutex for protecting all Boost.Python internal state +// Similar to pybind11's internals.mutex +BOOST_PYTHON_DECL pymutex& get_global_mutex(); + +// Macro for acquiring the global lock +// Similar to pybind11's PYBIND11_LOCK_INTERNALS +#define BOOST_PYTHON_LOCK_STATE() \ + ::boost::python::detail::pymutex_guard lock(::boost::python::detail::get_global_mutex()) + +#else + +// No-op macro when not in free-threaded mode +#define BOOST_PYTHON_LOCK_STATE() + +#endif // Py_GIL_DISABLED + +}}} // namespace boost::python::detail + +#endif // BOOST_PYTHON_DETAIL_PYMUTEX_HPP diff --git a/src/converter/from_python.cpp b/src/converter/from_python.cpp index f3989ba7..53a149fa 100644 --- a/src/converter/from_python.cpp +++ b/src/converter/from_python.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -145,6 +146,8 @@ namespace inline bool visit(rvalue_from_python_chain const* chain) { + BOOST_PYTHON_LOCK_STATE(); + visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain); if (p != visited.end() && *p == chain) return false; @@ -157,9 +160,11 @@ namespace { unvisit(rvalue_from_python_chain const* chain) : chain(chain) {} - + ~unvisit() { + BOOST_PYTHON_LOCK_STATE(); + visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain); assert(p != visited.end()); visited.erase(p); diff --git a/src/converter/registry.cpp b/src/converter/registry.cpp index aa20c3f6..1b23dbef 100644 --- a/src/converter/registry.cpp +++ b/src/converter/registry.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -112,9 +113,9 @@ registration::~registration() namespace // { typedef registration entry; - + typedef std::set registry_t; - + #ifndef BOOST_PYTHON_CONVERTER_REGISTRY_APPLE_MACH_WORKAROUND registry_t& entries() { @@ -181,6 +182,8 @@ namespace // entry* get(type_info type, bool is_shared_ptr = false) { + BOOST_PYTHON_LOCK_STATE(); + # ifdef BOOST_PYTHON_TRACE_REGISTRY registry_t::iterator p = entries().find(entry(type)); @@ -293,6 +296,8 @@ namespace registry registration const* query(type_info type) { + BOOST_PYTHON_LOCK_STATE(); + registry_t::iterator p = entries().find(entry(type)); # ifdef BOOST_PYTHON_TRACE_REGISTRY std::cout << "querying " << type diff --git a/src/converter/type_id.cpp b/src/converter/type_id.cpp index c6a8bf7a..fafb1361 100644 --- a/src/converter/type_id.cpp +++ b/src/converter/type_id.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -81,7 +82,7 @@ namespace { free_mem(char*p) : p(p) {} - + ~free_mem() { std::free(p); @@ -92,6 +93,7 @@ namespace bool cxxabi_cxa_demangle_is_broken() { + BOOST_PYTHON_LOCK_STATE(); static bool was_tested = false; static bool is_broken = false; if (!was_tested) { @@ -109,6 +111,8 @@ namespace detail { BOOST_PYTHON_DECL char const* gcc_demangle(char const* mangled) { + BOOST_PYTHON_LOCK_STATE(); + typedef std::vector< std::pair > mangling_map; diff --git a/src/errors.cpp b/src/errors.cpp index 34ea22f4..7f6b1880 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -10,9 +10,21 @@ #include #include #include +#include namespace boost { namespace python { +#ifdef Py_GIL_DISABLED +namespace detail { + // Global mutex for protecting all Boost.Python internal state + pymutex& get_global_mutex() + { + static pymutex mutex; + return mutex; + } +} +#endif + error_already_set::~error_already_set() {} // IMPORTANT: this function may only be called from within a catch block! @@ -20,8 +32,13 @@ BOOST_PYTHON_DECL bool handle_exception_impl(function0 f) { try { - if (detail::exception_handler::chain) - return detail::exception_handler::chain->handle(f); + detail::exception_handler* handler_chain = nullptr; + { + BOOST_PYTHON_LOCK_STATE(); + handler_chain = detail::exception_handler::chain; + } + if (handler_chain) + return handler_chain->handle(f); f(); return false; } @@ -80,6 +97,7 @@ exception_handler::exception_handler(handler_function const& impl) : m_impl(impl) , m_next(0) { + BOOST_PYTHON_LOCK_STATE(); if (chain != 0) tail->m_next = this; else diff --git a/src/object/inheritance.cpp b/src/object/inheritance.cpp index a7b3156e..44062875 100644 --- a/src/object/inheritance.cpp +++ b/src/object/inheritance.cpp @@ -4,6 +4,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include #include +#include #include #if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179 # include @@ -390,6 +391,8 @@ namespace inline void* convert_type(void* const p, class_id src_t, class_id dst_t, bool polymorphic) { + BOOST_PYTHON_LOCK_STATE(); + // Quickly rule out unregistered types index_entry* src_p = seek_type(src_t); if (src_p == 0) @@ -452,6 +455,8 @@ BOOST_PYTHON_DECL void* find_static_type(void* p, class_id src_t, class_id dst_t BOOST_PYTHON_DECL void add_cast( class_id src_t, class_id dst_t, cast_function cast, bool is_downcast) { + BOOST_PYTHON_LOCK_STATE(); + // adding an edge will invalidate any record of unreachability in // the cache. static std::size_t expected_cache_len = 0; @@ -490,6 +495,7 @@ BOOST_PYTHON_DECL void add_cast( BOOST_PYTHON_DECL void register_dynamic_id_aux( class_id static_id, dynamic_id_function get_dynamic_id) { + BOOST_PYTHON_LOCK_STATE(); tuples::get(*demand_type(static_id)) = get_dynamic_id; }