Skip to content

Commit 80731af

Browse files
committed
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()) { ... }
1 parent fc68878 commit 80731af

File tree

5 files changed

+129
-6
lines changed

5 files changed

+129
-6
lines changed

include/boost/python/module_init.hpp

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,41 @@
1111

1212
# ifndef BOOST_PYTHON_MODULE_INIT
1313

14-
namespace boost { namespace python { namespace detail {
14+
namespace boost { namespace python {
15+
16+
#ifdef HAS_CXX11
17+
// Use to activate the Py_MOD_GIL_NOT_USED flag.
18+
class mod_gil_not_used {
19+
public:
20+
explicit mod_gil_not_used(bool flag = true) : flag_(flag) {}
21+
bool flag() const { return flag_; }
22+
23+
private:
24+
bool flag_;
25+
};
26+
27+
namespace detail {
28+
29+
inline bool gil_not_used_option() { return false; }
30+
template <typename F, typename... O>
31+
bool gil_not_used_option(F &&, O &&...o);
32+
template <typename... O>
33+
inline bool gil_not_used_option(mod_gil_not_used f, O &&...o) {
34+
return f.flag() || gil_not_used_option(o...);
35+
}
36+
template <typename F, typename... O>
37+
inline bool gil_not_used_option(F &&, O &&...o) {
38+
return gil_not_used_option(o...);
39+
}
40+
41+
}
42+
#endif // HAS_CXX11
43+
44+
namespace detail {
1545

1646
# if PY_VERSION_HEX >= 0x03000000
1747

18-
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)());
48+
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false);
1949

2050
#else
2151

@@ -27,7 +57,37 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
2757

2858
# if PY_VERSION_HEX >= 0x03000000
2959

30-
# define _BOOST_PYTHON_MODULE_INIT(name) \
60+
# ifdef HAS_CXX11
61+
# define _BOOST_PYTHON_MODULE_INIT(name, ...) \
62+
PyObject* BOOST_PP_CAT(PyInit_, name)() \
63+
{ \
64+
static PyModuleDef_Base initial_m_base = { \
65+
PyObject_HEAD_INIT(NULL) \
66+
0, /* m_init */ \
67+
0, /* m_index */ \
68+
0 /* m_copy */ }; \
69+
static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \
70+
\
71+
static struct PyModuleDef moduledef = { \
72+
initial_m_base, \
73+
BOOST_PP_STRINGIZE(name), \
74+
0, /* m_doc */ \
75+
-1, /* m_size */ \
76+
initial_methods, \
77+
0, /* m_reload */ \
78+
0, /* m_traverse */ \
79+
0, /* m_clear */ \
80+
0, /* m_free */ \
81+
}; \
82+
\
83+
return boost::python::detail::init_module( \
84+
moduledef, BOOST_PP_CAT(init_module_, name), \
85+
boost::python::detail::gil_not_used_option(__VA_ARGS__) ); \
86+
} \
87+
void BOOST_PP_CAT(init_module_, name)()
88+
89+
# else // !HAS_CXX11
90+
# define _BOOST_PYTHON_MODULE_INIT(name) \
3191
PyObject* BOOST_PP_CAT(PyInit_, name)() \
3292
{ \
3393
static PyModuleDef_Base initial_m_base = { \
@@ -53,6 +113,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
53113
moduledef, BOOST_PP_CAT(init_module_, name) ); \
54114
} \
55115
void BOOST_PP_CAT(init_module_, name)()
116+
# endif // HAS_CXX11
56117

57118
# else
58119

@@ -66,9 +127,15 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)());
66127

67128
# endif
68129

69-
# define BOOST_PYTHON_MODULE_INIT(name) \
130+
# ifdef HAS_CXX11
131+
# define BOOST_PYTHON_MODULE_INIT(name, ...) \
132+
void BOOST_PP_CAT(init_module_,name)(); \
133+
extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name, __VA_ARGS__)
134+
# else
135+
# define BOOST_PYTHON_MODULE_INIT(name) \
70136
void BOOST_PP_CAT(init_module_,name)(); \
71137
extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name)
138+
# endif // HAS_CXX11
72139

73140
# endif
74141

src/module.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char
3838

3939
#if PY_VERSION_HEX >= 0x03000000
4040

41-
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, void(*init_function)())
41+
BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef,
42+
void(*init_function)(), bool gil_not_used)
4243
{
4344
PyObject *mod = PyModule_Create(&moduledef);
4445
#ifdef Py_GIL_DISABLED
45-
if (mod != NULL) {
46+
if (mod != NULL && gil_not_used) {
4647
PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED);
4748
}
4849
#endif

test/fabscript

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ for t in [('injected',),
6868
('raw_ctor',),
6969
('exception_translator',),
7070
('module_init_exception',),
71+
('module_nogil',),
7172
('test_enum', ['enum_ext']),
7273
('test_cltree', ['cltree']),
7374
('newtest', ['m1', 'm2']),

test/module_nogil.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Test for BOOST_PYTHON_MODULE with optional mod_gil_not_used argument
2+
3+
#include <boost/python/module.hpp>
4+
#include <boost/python/def.hpp>
5+
6+
// Simple function to export
7+
int get_value() {
8+
return 1234;
9+
}
10+
11+
#ifdef HAS_CXX11
12+
// C++11 build: test with mod_gil_not_used option
13+
BOOST_PYTHON_MODULE(module_nogil_ext, boost::python::mod_gil_not_used())
14+
{
15+
using namespace boost::python;
16+
def("get_value", get_value);
17+
}
18+
#else
19+
// C++98 build: test without optional arguments
20+
BOOST_PYTHON_MODULE(module_nogil_ext)
21+
{
22+
using namespace boost::python;
23+
def("get_value", get_value);
24+
}
25+
#endif

test/module_nogil.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
>>> from module_nogil_ext import *
3+
>>> get_value()
4+
1234
5+
>>> import sys, sysconfig
6+
>>> Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED'))
7+
>>> if Py_GIL_DISABLED and sys._is_gil_enabled():
8+
... print('GIL is enabled and should not be')
9+
... else:
10+
... print('okay')
11+
okay
12+
"""
13+
14+
from __future__ import print_function
15+
16+
def run(args = None):
17+
import sys
18+
import doctest
19+
20+
if args is not None:
21+
sys.argv = args
22+
return doctest.testmod(sys.modules.get(__name__))
23+
24+
if __name__ == '__main__':
25+
print("running...")
26+
import sys
27+
status = run()[0]
28+
if (status == 0): print("Done.")
29+
sys.exit(status)

0 commit comments

Comments
 (0)