mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Support restarting the interpreter and subinterpreters
This commit is contained in:
parent
22c413b196
commit
931b9e93ab
@ -58,7 +58,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
|
|||||||
handle builtins(PyEval_GetBuiltins());
|
handle builtins(PyEval_GetBuiltins());
|
||||||
const char *id = PYBIND11_INTERNALS_ID;
|
const char *id = PYBIND11_INTERNALS_ID;
|
||||||
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
|
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
|
||||||
internals_ptr = capsule(builtins[id]);
|
internals_ptr = *static_cast<internals **>(capsule(builtins[id]));
|
||||||
} else {
|
} else {
|
||||||
internals_ptr = new internals();
|
internals_ptr = new internals();
|
||||||
#if defined(WITH_THREAD)
|
#if defined(WITH_THREAD)
|
||||||
@ -68,7 +68,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
|
|||||||
PyThread_set_key_value(internals_ptr->tstate, tstate);
|
PyThread_set_key_value(internals_ptr->tstate, tstate);
|
||||||
internals_ptr->istate = tstate->interp;
|
internals_ptr->istate = tstate->interp;
|
||||||
#endif
|
#endif
|
||||||
builtins[id] = capsule(internals_ptr);
|
builtins[id] = capsule(&internals_ptr);
|
||||||
internals_ptr->registered_exception_translators.push_front(
|
internals_ptr->registered_exception_translators.push_front(
|
||||||
[](std::exception_ptr p) -> void {
|
[](std::exception_ptr p) -> void {
|
||||||
try {
|
try {
|
||||||
|
@ -131,13 +131,29 @@ inline void initialize_interpreter(bool init_signal_handlers = true) {
|
|||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Python cannot unload binary extension modules. If `initialize_interpreter` is
|
The interpreter can be restarted by calling `initialize_interpreter` again.
|
||||||
called again to restart the interpreter, the initializers of those modules will
|
Modules created using pybind11 can be safely re-initialized. However, Python
|
||||||
be executed for a second time and they will fail. This is a known CPython issue.
|
itself cannot completely unload binary extension modules and there are several
|
||||||
See the Python documentation for details.
|
caveats with regard to interpreter restarting. All the details can be found
|
||||||
|
in the CPython documentation. In short, not all interpreter memory may be
|
||||||
|
freed, either due to reference cycles or user-created global data.
|
||||||
|
|
||||||
\endrst */
|
\endrst */
|
||||||
inline void finalize_interpreter() { Py_Finalize(); }
|
inline void finalize_interpreter() {
|
||||||
|
handle builtins(PyEval_GetBuiltins());
|
||||||
|
const char *id = PYBIND11_INTERNALS_ID;
|
||||||
|
|
||||||
|
detail::internals **internals_ptr_ptr = nullptr;
|
||||||
|
if (builtins.contains(id) && isinstance<capsule>(builtins[id]))
|
||||||
|
internals_ptr_ptr = capsule(builtins[id]);
|
||||||
|
|
||||||
|
Py_Finalize();
|
||||||
|
|
||||||
|
if (internals_ptr_ptr) {
|
||||||
|
delete *internals_ptr_ptr;
|
||||||
|
*internals_ptr_ptr = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
|
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
|
||||||
|
@ -26,6 +26,8 @@ PYBIND11_EMBEDDED_MODULE(widget_module, m) {
|
|||||||
py::class_<Widget, PyWidget>(m, "Widget")
|
py::class_<Widget, PyWidget>(m, "Widget")
|
||||||
.def(py::init<std::string>())
|
.def(py::init<std::string>())
|
||||||
.def_property_readonly("the_message", &Widget::the_message);
|
.def_property_readonly("the_message", &Widget::the_message);
|
||||||
|
|
||||||
|
m.def("add", [](int i, int j) { return i + j; });
|
||||||
}
|
}
|
||||||
|
|
||||||
PYBIND11_EMBEDDED_MODULE(throw_exception, ) {
|
PYBIND11_EMBEDDED_MODULE(throw_exception, ) {
|
||||||
@ -81,3 +83,73 @@ TEST_CASE("There can be only one interpreter") {
|
|||||||
}
|
}
|
||||||
py::initialize_interpreter();
|
py::initialize_interpreter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_pybind11_internals() {
|
||||||
|
auto builtins = py::handle(PyEval_GetBuiltins());
|
||||||
|
return builtins.contains(PYBIND11_INTERNALS_ID);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("Restart the interpreter") {
|
||||||
|
// Verify pre-restart state.
|
||||||
|
REQUIRE(py::module::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
|
||||||
|
REQUIRE(has_pybind11_internals());
|
||||||
|
|
||||||
|
// Restart the interpreter.
|
||||||
|
py::finalize_interpreter();
|
||||||
|
REQUIRE(Py_IsInitialized() == 0);
|
||||||
|
|
||||||
|
py::initialize_interpreter();
|
||||||
|
REQUIRE(Py_IsInitialized() == 1);
|
||||||
|
|
||||||
|
// Internals are deleted after a restart.
|
||||||
|
REQUIRE_FALSE(has_pybind11_internals());
|
||||||
|
pybind11::detail::get_internals();
|
||||||
|
REQUIRE(has_pybind11_internals());
|
||||||
|
|
||||||
|
// C++ modules can be reloaded.
|
||||||
|
auto cpp_module = py::module::import("widget_module");
|
||||||
|
REQUIRE(cpp_module.attr("add")(1, 2).cast<int>() == 3);
|
||||||
|
|
||||||
|
// C++ type information is reloaded and can be used in python modules.
|
||||||
|
auto py_module = py::module::import("test_interpreter");
|
||||||
|
auto py_widget = py_module.attr("DerivedWidget")("Hello after restart");
|
||||||
|
REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Subinterpreter") {
|
||||||
|
// Add tags to the modules in the main interpreter and test the basics.
|
||||||
|
py::module::import("__main__").attr("main_tag") = "main interpreter";
|
||||||
|
{
|
||||||
|
auto m = py::module::import("widget_module");
|
||||||
|
m.attr("extension_module_tag") = "added to module in main interpreter";
|
||||||
|
|
||||||
|
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
|
||||||
|
}
|
||||||
|
REQUIRE(has_pybind11_internals());
|
||||||
|
|
||||||
|
/// Create and switch to a subinterpreter.
|
||||||
|
auto main_tstate = PyThreadState_Get();
|
||||||
|
auto sub_tstate = Py_NewInterpreter();
|
||||||
|
|
||||||
|
// Subinterpreters get their own copy of builtins. detail::get_internals() still
|
||||||
|
// works by returning from the static variable, i.e. all interpreters share a single
|
||||||
|
// global pybind11::internals;
|
||||||
|
REQUIRE_FALSE(has_pybind11_internals());
|
||||||
|
|
||||||
|
// Modules tags should be gone.
|
||||||
|
REQUIRE_FALSE(py::hasattr(py::module::import("__main__"), "tag"));
|
||||||
|
{
|
||||||
|
auto m = py::module::import("widget_module");
|
||||||
|
REQUIRE_FALSE(py::hasattr(m, "extension_module_tag"));
|
||||||
|
|
||||||
|
// Function bindings should still work.
|
||||||
|
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore main interpreter.
|
||||||
|
Py_EndInterpreter(sub_tstate);
|
||||||
|
PyThreadState_Swap(main_tstate);
|
||||||
|
|
||||||
|
REQUIRE(py::hasattr(py::module::import("__main__"), "main_tag"));
|
||||||
|
REQUIRE(py::hasattr(py::module::import("widget_module"), "extension_module_tag"));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user