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:
103
include/boost/python/detail/pymutex.hpp
Normal file
103
include/boost/python/detail/pymutex.hpp
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user