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

Compare commits

...

10 Commits

Author SHA1 Message Date
Stefan Seefeld
1ac9715633 Add python 2 to Ubuntu build matrix. 2025-11-28 21:35:11 -05:00
Neil Schemenauer
6a9c3416f7 Fix typo, should be "clang++". 2025-11-27 17:24:42 -08:00
Neil Schemenauer
b44fcf142b Simplify CI workflow.
We don't need test-ubuntu-py-ver.yml since we can use "include".  Test
Python 3.14t as well.  More Python versions can be tested now we are
using setup-python.
2025-11-27 13:42:55 -08:00
Neil Schemenauer
597df1d7ba Add test-ubuntu-py-ver.yml workflow. 2025-11-17 15:38:03 -08:00
Neil Schemenauer
b7b0137984 Update Linux CI scripts, more Python versions.
Update scripts to use actions/setup-python to install different Python
versions.  Add run-faber.sh and get-py-env.py scripts.  Add
test-ubuntu-py-ver.yml CI script to test with different Python versions.
2025-11-17 15:32:05 -08:00
Neil Schemenauer
80731af9ee Add "nogil" option for BOOST_PYTHON_MODULE_INIT.
Implement optional arguments for BOOST_PYTHON_MODULE_INIT and allow the
boost::python::mod_gil_not_used() option.  This sets the
Py_MOD_GIL_NOT_USED flag for the extension module.

To define a module that supports free-threaded Python, define it
like this:

    BOOST_PYTHON_MODULE(my_module, boost::python::mod_gil_not_used())
    {
        ...
    }
2025-11-06 19:48:14 -08:00
Neil Schemenauer
fc68878e02 Set the Py_MOD_GIL_NOT_USED flag on modules.
This indicates that the free-threaded build of Python can keep the
GIL disabled when the module is loaded.  Without this module flag,
importing the module will cause the GIL to be re-enabled.  A warning
is emitted if this happens.
2025-11-06 19:46:37 -08:00
Neil Schemenauer
cfbefe893c 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.
2025-11-06 19:43:33 -08:00
Neil Schemenauer
6f5f3b6607 Add work-around to crash in ~object_base().
For the free-threaded build (and possibly the debug build), it is not
safe to call Py_DECREF() if there is no valid Python thread-state.
2025-11-06 19:42:41 -08:00
Neil Schemenauer
cabb466057 Use strong reference APIs.
For the free-threaded build, it is not safe use borrowed references.
Another thread could deallocate the object and cause the reference to
become invalid.  Replace API calls that borrow references with strong
reference APIs.
2025-11-06 19:41:00 -08:00
18 changed files with 479 additions and 34 deletions

65
.github/get-py-env.py vendored Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
#
# Determine info about the Python install and write shell code to stdout, to
# set env variables. This will set the variables PY_LDFLAGS, PY_CFLAGS and
# PY_INC_PATH.
#
# The python3-config tool is used as the source of this info. In theory we
# could use sysconfig as well but the setup-python action from github appears
# to patch python3-config but not patch the sysconfig info.
#
# Usage:
# eval $(python3 get-py-env.py)
import os
import re
import subprocess
def get_output(cmd):
rv = subprocess.run(
cmd,
capture_output=True, # Capture stdout and stderr
text=True, # Decode output as text (UTF-8)
check=True, # Raise an error if the command fails
)
return rv.stdout
def extract_flags(cmd, prefix):
flags = []
for part in get_output(cmd).split():
part = part.strip()
if part.startswith(prefix):
flags.append(part)
return ' '.join(flags)
def find_python_h():
"""Find the include path that has Python.h contained inside.
We could use INCLUDEPY from sysconfig but github patches
python3-config but not the sysconfig info (after moving the
install).
"""
c_flags = extract_flags(['python3-config', '--cflags'], '-I')
for part in c_flags.split():
m = re.search(r'-I(\S+)', part)
if not m:
continue
inc_path = m.group(1)
if os.path.exists(os.path.join(inc_path, 'Python.h')):
return inc_path
raise SystemExit('cannot find Python.h')
def main():
ld_flags = extract_flags(['python3-config', '--ldflags'], '-L')
c_flags = extract_flags(['python3-config', '--cflags'], '-I')
include_path = find_python_h()
print(f'PY_LDFLAGS="{ld_flags}"')
print(f'PY_CFLAGS="{c_flags}"')
print(f'PY_INC_PATH="{include_path}"')
if __name__ == '__main__':
main()

46
.github/run-faber.sh vendored Executable file
View File

@@ -0,0 +1,46 @@
#!/bin/sh
set -eu
echo "cxx version: $CXX $($CXX --version)"
echo "cxx std: $CXX_STD"
echo "python3 path: $(which python3)"
echo "python3 version: $(python3 --version)"
if ! which faber > /dev/null; then
echo "Installing faber..."
python3 -m pip install --upgrade pip
python3 -m pip install -U faber
fi
echo "faber version: $(faber -v)"
# find and set PY_LDFLAGS and PY_INC_PATH
eval $(python3 .github/get-py-env.py)
echo "PY_INC_PATH=$PY_INC_PATH"
echo "PY_LDFLAGS=$PY_LDFLAGS"
case $(python3-config --abiflags) in
*t*)
# When running with free-threaded, we always want to disable the GIL
# even for extensions without the mod_gil_not_used() flag
export PYTHON_GIL=0
;;
esac
# this could be set by LD_LIBRARY_PATH but faber overrides it
prefix=$(python3-config --prefix)
echo "${prefix}/lib" > /etc/ld.so.conf.d/boost-ci.conf && ldconfig
sed -e "s/\$PYTHON/python3/g" .ci/faber > $HOME/.faber
faber \
--with-boost-include=${BOOST_PY_DEPS} \
--builddir=build \
cxx.name="${CXX}" \
cxxflags="-std=${CXX_STD}" \
cppflags="-std=${CXX_STD}" \
include="${PY_INC_PATH}" \
ldflags="${PY_LDFLAGS}" \
-j`nproc` \
"$@"

View File

@@ -1,6 +1,10 @@
# Test on Ubuntu with various compiler and language standard versions.
name: Test Ubuntu name: Test Ubuntu
on: [push, pull_request] on:
push:
pull_request:
workflow_dispatch:
jobs: jobs:
build: build:
@@ -9,35 +13,52 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python: [python, python3] python-version: ['3.14']
cxx: [g++, clang++] cxx: [g++, clang++]
std: [c++11, c++14, c++17] std: [c++11, c++14, c++17]
include: include:
# Add the appropriate docker image for each compiler. # Also test with free-threaded build of Python
# The images from teeks99/boost-python-test already have boost::python - python-version: '2.7'
# pre-reqs installed, see: cxx: g++
# https://github.com/teeks99/boost-python-test-docker std: c++11
- cxx: clang++ - python-version: '3.14t'
docker-img: teeks99/boost-python-test:clang-21_1.89.0 cxx: clang++
- cxx: g++ std: c++17
docker-img: teeks99/boost-python-test:gcc-15_1.89.0
container: container:
image: ${{ matrix.docker-img }} # Add the appropriate docker image for the compiler.
# The images from teeks99/boost-python-test already have boost::python
# pre-reqs installed, see:
# https://github.com/teeks99/boost-python-test-docker
image: ${{ matrix.cxx == 'g++' &&
'teeks99/boost-python-test:gcc-15_1.89.0' ||
'teeks99/boost-python-test:clang-21_1.89.0' }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: setup python
if: "${{ matrix.python-version != '2.7' }}"
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: setup prerequisites - name: setup prerequisites
run: | run: |
# Warning: this is not necessarily the same Python version as the one configured above ! # Warning: this is not necessarily the same Python version as the one configured above !
python3 -m pip install -U faber --break-system-packages python3 -m pip install -U faber --break-system-packages
echo "CXX=${{ matrix.cxx }}" >> "$GITHUB_ENV"
echo "CXX_STD=${{ matrix.std }}" >> "$GITHUB_ENV"
- name: build - name: build
if: "${{ matrix.python-version != '2.7' }}"
run: | run: |
${{ matrix.python }} --version .github/run-faber.sh
- name: build
if: "${{ matrix.python-version == '2.7' }}"
run: |
python --version
echo ${BOOST_PY_DEPS}
${{ matrix.cxx }} --version ${{ matrix.cxx }} --version
faber -v faber -v
sed -e "s/\$PYTHON/${{ matrix.python }}/g" .ci/faber > ~/.faber sed -e "s/\$PYTHON/python/g" .ci/faber > ~/.faber
faber \ faber \
--with-boost-include=${BOOST_PY_DEPS} \ --with-boost-include=${BOOST_PY_DEPS} \
--builddir=build \ --builddir=build \
@@ -46,8 +67,14 @@ jobs:
cppflags=-std=${{ matrix.std }} \ cppflags=-std=${{ matrix.std }} \
-j`nproc` -j`nproc`
- name: test - name: test
if: "${{ matrix.python-version != '2.7' }}"
run: |
.github/run-faber.sh test.report
- name: test
if: "${{ matrix.python-version == '2.7' }}"
run: | run: |
faber \ faber \
--debug \
--with-boost-include=${BOOST_PY_DEPS} \ --with-boost-include=${BOOST_PY_DEPS} \
--builddir=build \ --builddir=build \
cxx.name=${{ matrix.cxx }} \ cxx.name=${{ matrix.cxx }} \

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,11 +11,41 @@
# ifndef BOOST_PYTHON_MODULE_INIT # ifndef BOOST_PYTHON_MODULE_INIT
namespace boost { namespace python { namespace detail { namespace boost { namespace python {
#ifdef HAS_CXX11
// Use to activate the Py_MOD_GIL_NOT_USED flag.
class mod_gil_not_used {
public:
explicit mod_gil_not_used(bool flag = true) : flag_(flag) {}
bool flag() const { return flag_; }
private:
bool flag_;
};
namespace detail {
inline bool gil_not_used_option() { return false; }
template <typename F, typename... O>
bool gil_not_used_option(F &&, O &&...o);
template <typename... O>
inline bool gil_not_used_option(mod_gil_not_used f, O &&...o) {
return f.flag() || gil_not_used_option(o...);
}
template <typename F, typename... O>
inline bool gil_not_used_option(F &&, O &&...o) {
return gil_not_used_option(o...);
}
}
#endif // HAS_CXX11
namespace detail {
# if PY_VERSION_HEX >= 0x03000000 # if PY_VERSION_HEX >= 0x03000000
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)()); BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false);
#else #else
@@ -27,7 +57,37 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
# if PY_VERSION_HEX >= 0x03000000 # if PY_VERSION_HEX >= 0x03000000
# define _BOOST_PYTHON_MODULE_INIT(name) \ # ifdef HAS_CXX11
# define _BOOST_PYTHON_MODULE_INIT(name, ...) \
PyObject* BOOST_PP_CAT(PyInit_, name)() \
{ \
static PyModuleDef_Base initial_m_base = { \
PyObject_HEAD_INIT(NULL) \
0, /* m_init */ \
0, /* m_index */ \
0 /* m_copy */ }; \
static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \
\
static struct PyModuleDef moduledef = { \
initial_m_base, \
BOOST_PP_STRINGIZE(name), \
0, /* m_doc */ \
-1, /* m_size */ \
initial_methods, \
0, /* m_reload */ \
0, /* m_traverse */ \
0, /* m_clear */ \
0, /* m_free */ \
}; \
\
return boost::python::detail::init_module( \
moduledef, BOOST_PP_CAT(init_module_, name), \
boost::python::detail::gil_not_used_option(__VA_ARGS__) ); \
} \
void BOOST_PP_CAT(init_module_, name)()
# else // !HAS_CXX11
# define _BOOST_PYTHON_MODULE_INIT(name) \
PyObject* BOOST_PP_CAT(PyInit_, name)() \ PyObject* BOOST_PP_CAT(PyInit_, name)() \
{ \ { \
static PyModuleDef_Base initial_m_base = { \ static PyModuleDef_Base initial_m_base = { \
@@ -53,6 +113,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
moduledef, BOOST_PP_CAT(init_module_, name) ); \ moduledef, BOOST_PP_CAT(init_module_, name) ); \
} \ } \
void BOOST_PP_CAT(init_module_, name)() void BOOST_PP_CAT(init_module_, name)()
# endif // HAS_CXX11
# else # else
@@ -66,9 +127,15 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
# endif # endif
# define BOOST_PYTHON_MODULE_INIT(name) \ # ifdef HAS_CXX11
# define BOOST_PYTHON_MODULE_INIT(name, ...) \
void BOOST_PP_CAT(init_module_,name)(); \
extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name, __VA_ARGS__)
# else
# define BOOST_PYTHON_MODULE_INIT(name) \
void BOOST_PP_CAT(init_module_,name)(); \ void BOOST_PP_CAT(init_module_,name)(); \
extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name)
# endif // HAS_CXX11
# endif # endif

View File

@@ -419,6 +419,16 @@ inline api::object_base& api::object_base::operator=(api::object_base const& rhs
inline api::object_base::~object_base() inline api::object_base::~object_base()
{ {
#ifdef Py_GIL_DISABLED
// This is a not very elegant fix for a problem that occurs with the
// free-threaded build of Python. If this is called when the interpreter
// has already been finalized, the thread-state can be null. Unlike the
// GIL-enabled build, Py_DECREF() requires a valid thread-state. This
// causes a memory leak, rather than crash, which seems preferable.
if (PyThreadState_GetUnchecked() == NULL) {
return;
}
#endif
assert( Py_REFCNT(m_ptr) > 0 ); assert( Py_REFCNT(m_ptr) > 0 );
Py_DECREF(m_ptr); Py_DECREF(m_ptr);
} }

View File

@@ -11,6 +11,7 @@
#include <boost/python/handle.hpp> #include <boost/python/handle.hpp>
#include <boost/python/detail/raw_pyobject.hpp> #include <boost/python/detail/raw_pyobject.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <boost/python/cast.hpp> #include <boost/python/cast.hpp>
#include <vector> #include <vector>
@@ -145,6 +146,8 @@ namespace
inline bool visit(rvalue_from_python_chain const* chain) 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); visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain);
if (p != visited.end() && *p == chain) if (p != visited.end() && *p == chain)
return false; return false;
@@ -157,9 +160,11 @@ namespace
{ {
unvisit(rvalue_from_python_chain const* chain) unvisit(rvalue_from_python_chain const* chain)
: chain(chain) {} : chain(chain) {}
~unvisit() ~unvisit()
{ {
BOOST_PYTHON_LOCK_STATE();
visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain); visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain);
assert(p != visited.end()); assert(p != visited.end());
visited.erase(p); visited.erase(p);

View File

@@ -5,6 +5,7 @@
#include <boost/python/converter/registry.hpp> #include <boost/python/converter/registry.hpp>
#include <boost/python/converter/registrations.hpp> #include <boost/python/converter/registrations.hpp>
#include <boost/python/converter/builtin_converters.hpp> #include <boost/python/converter/builtin_converters.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <set> #include <set>
#include <stdexcept> #include <stdexcept>
@@ -112,9 +113,9 @@ registration::~registration()
namespace // <unnamed> namespace // <unnamed>
{ {
typedef registration entry; typedef registration entry;
typedef std::set<entry> registry_t; typedef std::set<entry> registry_t;
#ifndef BOOST_PYTHON_CONVERTER_REGISTRY_APPLE_MACH_WORKAROUND #ifndef BOOST_PYTHON_CONVERTER_REGISTRY_APPLE_MACH_WORKAROUND
registry_t& entries() registry_t& entries()
{ {
@@ -181,6 +182,8 @@ namespace // <unnamed>
entry* get(type_info type, bool is_shared_ptr = false) entry* get(type_info type, bool is_shared_ptr = false)
{ {
BOOST_PYTHON_LOCK_STATE();
# ifdef BOOST_PYTHON_TRACE_REGISTRY # ifdef BOOST_PYTHON_TRACE_REGISTRY
registry_t::iterator p = entries().find(entry(type)); registry_t::iterator p = entries().find(entry(type));
@@ -293,6 +296,8 @@ namespace registry
registration const* query(type_info type) registration const* query(type_info type)
{ {
BOOST_PYTHON_LOCK_STATE();
registry_t::iterator p = entries().find(entry(type)); registry_t::iterator p = entries().find(entry(type));
# ifdef BOOST_PYTHON_TRACE_REGISTRY # ifdef BOOST_PYTHON_TRACE_REGISTRY
std::cout << "querying " << type std::cout << "querying " << type

View File

@@ -5,6 +5,7 @@
#include <boost/python/type_id.hpp> #include <boost/python/type_id.hpp>
#include <boost/python/detail/decorated_type_id.hpp> #include <boost/python/detail/decorated_type_id.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
@@ -81,7 +82,7 @@ namespace
{ {
free_mem(char*p) free_mem(char*p)
: p(p) {} : p(p) {}
~free_mem() ~free_mem()
{ {
std::free(p); std::free(p);
@@ -92,6 +93,7 @@ namespace
bool cxxabi_cxa_demangle_is_broken() bool cxxabi_cxa_demangle_is_broken()
{ {
BOOST_PYTHON_LOCK_STATE();
static bool was_tested = false; static bool was_tested = false;
static bool is_broken = false; static bool is_broken = false;
if (!was_tested) { if (!was_tested) {
@@ -109,6 +111,8 @@ namespace detail
{ {
BOOST_PYTHON_DECL char const* gcc_demangle(char const* mangled) BOOST_PYTHON_DECL char const* gcc_demangle(char const* mangled)
{ {
BOOST_PYTHON_LOCK_STATE();
typedef std::vector< typedef std::vector<
std::pair<char const*, char const*> std::pair<char const*, char const*>
> mangling_map; > mangling_map;

View File

@@ -68,8 +68,16 @@ object dict_base::get(object_cref k) const
{ {
if (check_exact(this)) if (check_exact(this))
{ {
#ifdef Py_GIL_DISABLED
PyObject* result;
if (PyDict_GetItemRef(this->ptr(),k.ptr(),&result) < 0) {
throw_error_already_set();
}
return object(detail::new_reference(result ? result : Py_None));
#else
PyObject* result = PyDict_GetItem(this->ptr(),k.ptr()); PyObject* result = PyDict_GetItem(this->ptr(),k.ptr());
return object(detail::borrowed_reference(result ? result : Py_None)); return object(detail::borrowed_reference(result ? result : Py_None));
#endif
} }
else else
{ {

View File

@@ -10,9 +10,21 @@
#include <boost/python/errors.hpp> #include <boost/python/errors.hpp>
#include <boost/cast.hpp> #include <boost/cast.hpp>
#include <boost/python/detail/exception_handler.hpp> #include <boost/python/detail/exception_handler.hpp>
#include <boost/python/detail/pymutex.hpp>
namespace boost { namespace python { 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() {} error_already_set::~error_already_set() {}
// IMPORTANT: this function may only be called from within a catch block! // 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 try
{ {
if (detail::exception_handler::chain) detail::exception_handler* handler_chain = nullptr;
return detail::exception_handler::chain->handle(f); {
BOOST_PYTHON_LOCK_STATE();
handler_chain = detail::exception_handler::chain;
}
if (handler_chain)
return handler_chain->handle(f);
f(); f();
return false; return false;
} }
@@ -80,6 +97,7 @@ exception_handler::exception_handler(handler_function const& impl)
: m_impl(impl) : m_impl(impl)
, m_next(0) , m_next(0)
{ {
BOOST_PYTHON_LOCK_STATE();
if (chain != 0) if (chain != 0)
tail->m_next = this; tail->m_next = this;
else else

View File

@@ -38,10 +38,17 @@ BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, void(*init_function)()) BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef,
void(*init_function)(), bool gil_not_used)
{ {
PyObject *mod = PyModule_Create(&moduledef);
#ifdef Py_GIL_DISABLED
if (mod != NULL && gil_not_used) {
PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED);
}
#endif
return init_module_in_scope( return init_module_in_scope(
PyModule_Create(&moduledef), mod,
init_function); init_function);
} }

View File

@@ -135,7 +135,15 @@ namespace boost { namespace python { namespace objects {
str name(get_qualname(py_type)); str name(get_qualname(py_type));
if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE ) { if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE ) {
// Qualify the type name if it is defined in a different module. // Qualify the type name if it is defined in a different module.
PyObject *type_module_name = PyDict_GetItemString(py_type->tp_dict, "__module__"); PyObject *type_module_name;
#if PY_VERSION_HEX >= 0x030D0000
if (PyDict_GetItemStringRef(py_type->tp_dict, "__module__", &type_module_name) < 0) {
throw_error_already_set();
}
#else
type_module_name = PyDict_GetItemString(py_type->tp_dict, "__module__");
Py_XINCREF(type_module_name);
#endif
if ( if (
type_module_name type_module_name
&& PyObject_RichCompareBool( && PyObject_RichCompareBool(
@@ -144,8 +152,11 @@ namespace boost { namespace python { namespace objects {
Py_NE Py_NE
) != 0 ) != 0
) { ) {
return str("%s.%s" % make_tuple(handle<>(borrowed(type_module_name)), name)); str result = str("%s.%s" % make_tuple(handle<>(type_module_name), name));
return result;
} }
// Clean up the strong reference if we didn't use it
Py_XDECREF(type_module_name);
} }
return name; return name;
} else { } else {

View File

@@ -4,6 +4,7 @@
// http://www.boost.org/LICENSE_1_0.txt) // http://www.boost.org/LICENSE_1_0.txt)
#include <boost/python/object/inheritance.hpp> #include <boost/python/object/inheritance.hpp>
#include <boost/python/type_id.hpp> #include <boost/python/type_id.hpp>
#include <boost/python/detail/pymutex.hpp>
#include <boost/graph/breadth_first_search.hpp> #include <boost/graph/breadth_first_search.hpp>
#if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179 #if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179
# include <boost/graph/reverse_graph.hpp> # 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) 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 // Quickly rule out unregistered types
index_entry* src_p = seek_type(src_t); index_entry* src_p = seek_type(src_t);
if (src_p == 0) 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( BOOST_PYTHON_DECL void add_cast(
class_id src_t, class_id dst_t, cast_function cast, bool is_downcast) 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 // adding an edge will invalidate any record of unreachability in
// the cache. // the cache.
static std::size_t expected_cache_len = 0; 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( BOOST_PYTHON_DECL void register_dynamic_id_aux(
class_id static_id, dynamic_id_function get_dynamic_id) 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; tuples::get<kdynamic_id>(*demand_type(static_id)) = get_dynamic_id;
} }

View File

@@ -21,20 +21,28 @@ namespace detail
this->m_self, const_cast<char*>(name)))) this->m_self, const_cast<char*>(name))))
) )
{ {
PyObject* borrowed_f = 0; PyObject* class_f = 0;
if ( if (
PyMethod_Check(m.get()) PyMethod_Check(m.get())
&& PyMethod_GET_SELF(m.get()) == this->m_self && PyMethod_GET_SELF(m.get()) == this->m_self
&& class_object->tp_dict != 0 && class_object->tp_dict != 0
) )
{ {
borrowed_f = ::PyDict_GetItemString( #if PY_VERSION_HEX >= 0x030D0000
if (::PyDict_GetItemStringRef(
class_object->tp_dict, const_cast<char*>(name), &class_f) < 0) {
throw_error_already_set();
}
#else
class_f = ::PyDict_GetItemString(
class_object->tp_dict, const_cast<char*>(name)); class_object->tp_dict, const_cast<char*>(name));
Py_XINCREF(class_f);
#endif
} }
if (borrowed_f != PyMethod_GET_FUNCTION(m.get())) bool is_override = (class_f != PyMethod_GET_FUNCTION(m.get()));
Py_XDECREF(class_f);
if (is_override)
return override(m); return override(m);
} }
} }

View File

@@ -68,6 +68,7 @@ for t in [('injected',),
('raw_ctor',), ('raw_ctor',),
('exception_translator',), ('exception_translator',),
('module_init_exception',), ('module_init_exception',),
('module_nogil',),
('test_enum', ['enum_ext']), ('test_enum', ['enum_ext']),
('test_cltree', ['cltree']), ('test_cltree', ['cltree']),
('newtest', ['m1', 'm2']), ('newtest', ['m1', 'm2']),

25
test/module_nogil.cpp Normal file
View File

@@ -0,0 +1,25 @@
// Test for BOOST_PYTHON_MODULE with optional mod_gil_not_used argument
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
// Simple function to export
int get_value() {
return 1234;
}
#ifdef HAS_CXX11
// C++11 build: test with mod_gil_not_used option
BOOST_PYTHON_MODULE(module_nogil_ext, boost::python::mod_gil_not_used())
{
using namespace boost::python;
def("get_value", get_value);
}
#else
// C++98 build: test without optional arguments
BOOST_PYTHON_MODULE(module_nogil_ext)
{
using namespace boost::python;
def("get_value", get_value);
}
#endif

29
test/module_nogil.py Normal file
View File

@@ -0,0 +1,29 @@
"""
>>> from module_nogil_ext import *
>>> get_value()
1234
>>> import sys, sysconfig
>>> Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED'))
>>> if Py_GIL_DISABLED and sys._is_gil_enabled():
... print('GIL is enabled and should not be')
... else:
... print('okay')
okay
"""
from __future__ import print_function
def run(args = None):
import sys
import doctest
if args is not None:
sys.argv = args
return doctest.testmod(sys.modules.get(__name__))
if __name__ == '__main__':
print("running...")
import sys
status = run()[0]
if (status == 0): print("Done.")
sys.exit(status)