2
0
mirror of https://github.com/boostorg/python.git synced 2026-01-19 04:22:16 +00:00

Use re-entrant mutex to protect global state.

Add pymutex.hpp which implements a re-entrant mutex on top of Python's
PyMutex.  Add BOOST_PYTHON_LOCK_STATE() macro that uses RAII to lock
mutable global state as required.
This commit is contained in:
Neil Schemenauer
2025-10-13 22:42:42 -07:00
parent 6f5f3b6607
commit cfbefe893c
6 changed files with 147 additions and 6 deletions

View File

@@ -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 <boost/python/detail/prefix.hpp>
#ifdef Py_GIL_DISABLED
// needed for pymutex wrapper
#include <atomic>
#include <cstddef>
#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<unsigned long> 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

View File

@@ -11,6 +11,7 @@
#include <boost/python/handle.hpp>
#include <boost/python/detail/raw_pyobject.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <boost/python/cast.hpp>
#include <vector>
@@ -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);

View File

@@ -5,6 +5,7 @@
#include <boost/python/converter/registry.hpp>
#include <boost/python/converter/registrations.hpp>
#include <boost/python/converter/builtin_converters.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <set>
#include <stdexcept>
@@ -112,9 +113,9 @@ registration::~registration()
namespace // <unnamed>
{
typedef registration entry;
typedef std::set<entry> registry_t;
#ifndef BOOST_PYTHON_CONVERTER_REGISTRY_APPLE_MACH_WORKAROUND
registry_t& entries()
{
@@ -181,6 +182,8 @@ namespace // <unnamed>
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

View File

@@ -5,6 +5,7 @@
#include <boost/python/type_id.hpp>
#include <boost/python/detail/decorated_type_id.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <utility>
#include <vector>
#include <algorithm>
@@ -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<char const*, char const*>
> mangling_map;

View File

@@ -10,9 +10,21 @@
#include <boost/python/errors.hpp>
#include <boost/cast.hpp>
#include <boost/python/detail/exception_handler.hpp>
#include <boost/python/detail/pymutex.hpp>
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<void> 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

View File

@@ -4,6 +4,7 @@
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/python/object/inheritance.hpp>
#include <boost/python/type_id.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <boost/graph/breadth_first_search.hpp>
#if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179
# include <boost/graph/reverse_graph.hpp>
@@ -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<kdynamic_id>(*demand_type(static_id)) = get_dynamic_id;
}