mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
* Add per-interpreter storage for `gil_safe_call_once_and_store`
* Disable thread local cache for `internals_pp_manager`
* Disable thread local cache for `internals_pp_manager` for multi-interpreter only
* Use anonymous namespace to separate these type_ids from other tests with the same class names.
* style: pre-commit fixes
* Revert internals_pp_manager changes
* This is the crux of fix for the subinterpreter_before_main failure.
The pre_init needs to check if it is in a subinterpreter or not. But in 3.13+ this static initializer runs in the main interpreter. So we need to check this later, during the exec phase.
* Continue to do the ensure in both places, there might be a reason it was where it was...
Should not hurt anything to do it extra times here.
* Change get_num_interpreters_seen to a boolean flag instead.
The count was not used, it was just checked for > 1, we now accomplish this by setting the flag.
* Spelling typo
* Work around older python versions, only need this check for newish versions
* Add more comments for test case
* Add more comments for test case
* Stop traceback propagation
* Re-enable subinterpreter support on ubuntu 3.14 builds
Was disabled in e4873e8
* As suggested, don't use an anonymous namespace.
* Typo in test assert format string
* Use a more appropriate function name
* Fix mod_per_interpreter_gil* output directory on Windows/MSVC
On Windows with MSVC (multi-configuration generators), CMake uses
config-specific properties like LIBRARY_OUTPUT_DIRECTORY_DEBUG when
set, otherwise falls back to LIBRARY_OUTPUT_DIRECTORY/<Config>/.
The main test modules (pybind11_tests, etc.) correctly set both
LIBRARY_OUTPUT_DIRECTORY and the config-specific variants (lines
517-528), so they output directly to tests/.
However, the mod_per_interpreter_gil* modules only copied the base
LIBRARY_OUTPUT_DIRECTORY property, causing them to be placed in
tests/Debug/ instead of tests/.
This mismatch caused test_import_in_subinterpreter_concurrently and
related tests to fail with ModuleNotFoundError on Windows Python 3.14,
because the test code sets sys.path based on pybind11_tests.__file__
(which is in tests/) but tries to import mod_per_interpreter_gil_with_singleton
(which ended up in tests/Debug/).
This bug was previously masked by @pytest.mark.xfail decorators on
these tests. Now that the underlying "Duplicate C++ type registration"
issue is fixed and the xfails are removed, this path issue surfaced.
The fix mirrors the same pattern used for main test targets: also set
LIBRARY_OUTPUT_DIRECTORY_<CONFIG> for each configuration type.
* Remove unneeded `pytest.importorskip`
* Remove comment
---------
Co-authored-by: b-pass <b-pass@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
148 lines
5.1 KiB
C++
148 lines
5.1 KiB
C++
#include <pybind11/pybind11.h>
|
|
#include <pybind11/stl.h>
|
|
|
|
#include <vector>
|
|
|
|
namespace py = pybind11;
|
|
|
|
#ifdef PYBIND11_HAS_NATIVE_ENUM
|
|
# include <pybind11/native_enum.h>
|
|
#endif
|
|
|
|
namespace pybind11_tests {
|
|
namespace mod_per_interpreter_gil_with_singleton {
|
|
// A singleton class that holds references to certain Python objects
|
|
// This singleton is per-interpreter using gil_safe_call_once_and_store
|
|
class MySingleton {
|
|
public:
|
|
MySingleton() = default;
|
|
~MySingleton() = default;
|
|
MySingleton(const MySingleton &) = delete;
|
|
MySingleton &operator=(const MySingleton &) = delete;
|
|
MySingleton(MySingleton &&) = default;
|
|
MySingleton &operator=(MySingleton &&) = default;
|
|
|
|
static MySingleton &get_instance() {
|
|
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<MySingleton> storage;
|
|
return storage
|
|
.call_once_and_store_result([]() -> MySingleton {
|
|
MySingleton instance{};
|
|
|
|
auto emplace = [&instance](const py::handle &obj) -> void {
|
|
obj.inc_ref(); // Ensure the object is not GC'd while interpreter is alive
|
|
instance.objects.emplace_back(obj);
|
|
};
|
|
|
|
// Example objects to store in the singleton
|
|
emplace(py::type::handle_of(py::none())); // static type
|
|
emplace(py::type::handle_of(py::tuple())); // static type
|
|
emplace(py::type::handle_of(py::list())); // static type
|
|
emplace(py::type::handle_of(py::dict())); // static type
|
|
emplace(py::module_::import("collections").attr("OrderedDict")); // static type
|
|
emplace(py::module_::import("collections").attr("defaultdict")); // heap type
|
|
emplace(py::module_::import("collections").attr("deque")); // heap type
|
|
|
|
assert(instance.objects.size() == 7);
|
|
return instance;
|
|
})
|
|
.get_stored();
|
|
}
|
|
|
|
std::vector<py::handle> &get_objects() { return objects; }
|
|
|
|
static void init() {
|
|
// Ensure the singleton is created
|
|
auto &instance = get_instance();
|
|
(void) instance; // suppress unused variable warning
|
|
assert(instance.objects.size() == 7);
|
|
// Register cleanup at interpreter exit
|
|
py::module_::import("atexit").attr("register")(py::cpp_function(&MySingleton::clear));
|
|
}
|
|
|
|
static void clear() {
|
|
auto &instance = get_instance();
|
|
(void) instance; // suppress unused variable warning
|
|
assert(instance.objects.size() == 7);
|
|
for (const auto &obj : instance.objects) {
|
|
obj.dec_ref();
|
|
}
|
|
instance.objects.clear();
|
|
}
|
|
|
|
private:
|
|
std::vector<py::handle> objects;
|
|
};
|
|
|
|
class MyClass {
|
|
public:
|
|
explicit MyClass(py::ssize_t v) : value(v) {}
|
|
py::ssize_t get_value() const { return value; }
|
|
|
|
private:
|
|
py::ssize_t value;
|
|
};
|
|
|
|
class MyGlobalError : public std::runtime_error {
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
class MyLocalError : public std::runtime_error {
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
enum class MyEnum : int {
|
|
ONE = 1,
|
|
TWO = 2,
|
|
THREE = 3,
|
|
};
|
|
} // namespace mod_per_interpreter_gil_with_singleton
|
|
} // namespace pybind11_tests
|
|
|
|
PYBIND11_MODULE(mod_per_interpreter_gil_with_singleton,
|
|
m,
|
|
py::mod_gil_not_used(),
|
|
py::multiple_interpreters::per_interpreter_gil()) {
|
|
using namespace pybind11_tests::mod_per_interpreter_gil_with_singleton;
|
|
|
|
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
|
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = true;
|
|
#else
|
|
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = false;
|
|
#endif
|
|
|
|
MySingleton::init();
|
|
|
|
// Ensure py::multiple_interpreters::per_interpreter_gil() works with singletons using
|
|
// py::gil_safe_call_once_and_store
|
|
m.def(
|
|
"get_objects_in_singleton",
|
|
[]() -> std::vector<py::handle> { return MySingleton::get_instance().get_objects(); },
|
|
"Get the list of objects stored in the singleton");
|
|
|
|
// Ensure py::multiple_interpreters::per_interpreter_gil() works with class bindings
|
|
py::class_<MyClass>(m, "MyClass")
|
|
.def(py::init<py::ssize_t>())
|
|
.def("get_value", &MyClass::get_value);
|
|
|
|
// Ensure py::multiple_interpreters::per_interpreter_gil() works with global exceptions
|
|
py::register_exception<MyGlobalError>(m, "MyGlobalError");
|
|
// Ensure py::multiple_interpreters::per_interpreter_gil() works with local exceptions
|
|
py::register_local_exception<MyLocalError>(m, "MyLocalError");
|
|
|
|
#ifdef PYBIND11_HAS_NATIVE_ENUM
|
|
// Ensure py::multiple_interpreters::per_interpreter_gil() works with native_enum
|
|
py::native_enum<MyEnum>(m, "MyEnum", "enum.IntEnum")
|
|
.value("ONE", MyEnum::ONE)
|
|
.value("TWO", MyEnum::TWO)
|
|
.value("THREE", MyEnum::THREE)
|
|
.finalize();
|
|
#else
|
|
py::enum_<MyEnum>(m, "MyEnum")
|
|
.value("ONE", MyEnum::ONE)
|
|
.value("TWO", MyEnum::TWO)
|
|
.value("THREE", MyEnum::THREE);
|
|
#endif
|
|
}
|