Skip to content

Commit 97b50fe

Browse files
committed
Add more tests for multiple interpreters
1 parent 5ce00e5 commit 97b50fe

File tree

3 files changed

+370
-31
lines changed

3 files changed

+370
-31
lines changed

tests/test_multiple_interpreters/CMakeLists.txt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,32 @@
77

88
set(PYBIND11_MULTIPLE_INTERPRETERS_TEST_FILES test_multiple_interpreters.py)
99

10+
set(modules mod_per_interpreter_gil mod_shared_interpreter_gil
11+
mod_per_interpreter_gil_with_singleton)
12+
1013
# These modules don't get mixed with other test modules because those aren't subinterpreter safe.
11-
pybind11_add_module(mod_per_interpreter_gil THIN_LTO mod_per_interpreter_gil.cpp)
12-
pybind11_add_module(mod_shared_interpreter_gil THIN_LTO mod_shared_interpreter_gil.cpp)
13-
pybind11_enable_warnings(mod_per_interpreter_gil)
14-
pybind11_enable_warnings(mod_shared_interpreter_gil)
14+
foreach(mod IN LISTS modules)
15+
pybind11_add_module("${mod}" THIN_LTO "${mod}.cpp")
16+
pybind11_enable_warnings("${mod}")
17+
endforeach()
1518

1619
# Put the built modules next to `pybind11_tests.so` so that the test scripts can find them.
1720
get_target_property(pybind11_tests_output_directory pybind11_tests LIBRARY_OUTPUT_DIRECTORY)
18-
set_target_properties(mod_per_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
19-
"${pybind11_tests_output_directory}")
20-
set_target_properties(mod_shared_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
21-
"${pybind11_tests_output_directory}")
21+
foreach(mod IN LISTS modules)
22+
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY
23+
"${pybind11_tests_output_directory}")
24+
endforeach()
2225

2326
if(PYBIND11_TEST_SMART_HOLDER)
24-
target_compile_definitions(
25-
mod_per_interpreter_gil
26-
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
27-
target_compile_definitions(
28-
mod_shared_interpreter_gil
29-
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
27+
foreach(mod IN LISTS modules)
28+
target_compile_definitions(
29+
"${mod}"
30+
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
31+
)
32+
endforeach()
3033
endif()
3134

32-
add_dependencies(pytest mod_per_interpreter_gil mod_shared_interpreter_gil)
35+
add_dependencies(pytest ${modules})
3336

3437
# Convert relative to full file names and add to pytest test files
3538
list(TRANSFORM PYBIND11_MULTIPLE_INTERPRETERS_TEST_FILES PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/")
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include <pybind11/pybind11.h>
2+
#include <pybind11/stl.h>
3+
4+
#include <vector>
5+
6+
namespace py = pybind11;
7+
8+
#ifdef PYBIND11_HAS_NATIVE_ENUM
9+
# include <pybind11/native_enum.h>
10+
#endif
11+
12+
// A singleton class that holds references to certain Python objects
13+
// This singleton is per-interpreter using gil_safe_call_once_and_store
14+
class MySingleton {
15+
public:
16+
MySingleton() = default;
17+
~MySingleton() = default;
18+
19+
static MySingleton &get_instance() {
20+
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<MySingleton> storage;
21+
return storage
22+
.call_once_and_store_result([]() {
23+
MySingleton instance{};
24+
25+
auto emplace = [&instance](const py::handle &obj) -> void {
26+
obj.inc_ref(); // Ensure the object is not GC'd while interpreter is alive
27+
instance.objects.emplace_back(obj);
28+
};
29+
30+
// Example objects to store in the singleton
31+
emplace(py::type::handle_of(py::none())); // static type
32+
emplace(py::type::handle_of(py::tuple())); // static type
33+
emplace(py::type::handle_of(py::list())); // static type
34+
emplace(py::type::handle_of(py::dict())); // static type
35+
emplace(py::module_::import("collections").attr("OrderedDict")); // static type
36+
emplace(py::module_::import("collections").attr("defaultdict")); // heap type
37+
emplace(py::module_::import("collections").attr("deque")); // heap type
38+
return instance;
39+
})
40+
.get_stored();
41+
}
42+
43+
std::vector<py::handle> &get_objects() { return objects; }
44+
45+
static void init() {
46+
// Ensures the singleton is created
47+
get_instance();
48+
// Register cleanup at interpreter exit
49+
py::module_::import("atexit").attr("register")(py::cpp_function(&MySingleton::clear));
50+
}
51+
52+
static void clear() {
53+
auto &instance = get_instance();
54+
for (const auto &obj : instance.objects) {
55+
obj.dec_ref();
56+
}
57+
instance.objects.clear();
58+
}
59+
60+
private:
61+
std::vector<py::handle> objects;
62+
};
63+
64+
class MyClass {
65+
public:
66+
explicit MyClass(py::ssize_t v) : value(v) {}
67+
py::ssize_t get_value() const { return value; }
68+
69+
private:
70+
py::ssize_t value;
71+
};
72+
73+
class MyGlobalError : public std::runtime_error {
74+
public:
75+
using std::runtime_error::runtime_error;
76+
};
77+
78+
class MyLocalError : public std::runtime_error {
79+
public:
80+
using std::runtime_error::runtime_error;
81+
};
82+
83+
enum class MyEnum : int {
84+
ONE = 1,
85+
TWO = 2,
86+
THREE = 3,
87+
};
88+
89+
PYBIND11_MODULE(mod_per_interpreter_gil_with_singleton,
90+
m,
91+
py::mod_gil_not_used(),
92+
py::multiple_interpreters::per_interpreter_gil()) {
93+
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
94+
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = true;
95+
#else
96+
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = false;
97+
#endif
98+
99+
MySingleton::init();
100+
101+
// Ensure py::multiple_interpreters::per_interpreter_gil() works with singletons using
102+
// py::gil_safe_call_once_and_store
103+
m.def(
104+
"get_objects_in_singleton",
105+
[]() -> std::vector<py::handle> { return MySingleton::get_instance().get_objects(); },
106+
"Get the list of objects stored in the singleton");
107+
108+
// Ensure py::multiple_interpreters::per_interpreter_gil() works with class bindings
109+
py::class_<MyClass>(m, "MyClass")
110+
.def(py::init<py::ssize_t>())
111+
.def("get_value", &MyClass::get_value);
112+
113+
// Ensure py::multiple_interpreters::per_interpreter_gil() works with global exceptions
114+
py::register_exception<MyGlobalError>(m, "MyGlobalError");
115+
// Ensure py::multiple_interpreters::per_interpreter_gil() works with local exceptions
116+
py::register_local_exception<MyLocalError>(m, "MyLocalError");
117+
118+
#ifdef PYBIND11_HAS_NATIVE_ENUM
119+
// Ensure py::multiple_interpreters::per_interpreter_gil() works with native_enum
120+
py::native_enum<MyEnum>(m, "MyEnum", "enum.IntEnum")
121+
.value("ONE", MyEnum::ONE)
122+
.value("TWO", MyEnum::TWO)
123+
.value("THREE", MyEnum::THREE)
124+
.finalize();
125+
#else
126+
py::enum_<MyEnum>(m, "MyEnum")
127+
.value("ONE", MyEnum::ONE)
128+
.value("TWO", MyEnum::TWO)
129+
.value("THREE", MyEnum::THREE);
130+
#endif
131+
}

0 commit comments

Comments
 (0)