Skip to content

Commit 305a293

Browse files
committed
Add isolation and gc test for gil_safe_call_once_and_store
1 parent 58c08ac commit 305a293

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

tests/test_with_catch/test_subinterpreter.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,30 @@ void unsafe_reset_internals_for_single_interpreter() {
4040
py::detail::get_local_internals();
4141
}
4242

43+
py::object &get_dict_type_object() {
44+
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
45+
return storage
46+
.call_once_and_store_result(
47+
[]() -> py::object { return py::module_::import("builtins").attr("dict"); })
48+
.get_stored();
49+
}
50+
51+
py::object &get_ordered_dict_type_object() {
52+
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
53+
return storage
54+
.call_once_and_store_result(
55+
[]() -> py::object { return py::module_::import("collections").attr("OrderedDict"); })
56+
.get_stored();
57+
}
58+
59+
py::object &get_default_dict_type_object() {
60+
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
61+
return storage
62+
.call_once_and_store_result(
63+
[]() -> py::object { return py::module_::import("collections").attr("defaultdict"); })
64+
.get_stored();
65+
}
66+
4367
TEST_CASE("Single Subinterpreter") {
4468
unsafe_reset_internals_for_single_interpreter();
4569

@@ -324,9 +348,15 @@ TEST_CASE("gil_safe_call_once_and_store per-interpreter isolation") {
324348
.get_stored();
325349
REQUIRE(main_value.cast<py::interpid_t>() == main_interp_id);
326350

351+
py::object dict_type = get_dict_type_object();
352+
py::object ordered_dict_type = get_ordered_dict_type_object();
353+
py::object default_dict_type = get_default_dict_type_object();
354+
327355
py::interpid_t sub_interp_id = -1;
328356
py::interpid_t sub_cached_value = -1;
329357

358+
bool sub_default_dict_type_destroyed = false;
359+
330360
// Create a subinterpreter and check that it gets its own storage
331361
{
332362
py::scoped_subinterpreter ssi;
@@ -351,12 +381,46 @@ TEST_CASE("gil_safe_call_once_and_store per-interpreter isolation") {
351381
// This would fail without per-interpreter storage.
352382
REQUIRE(sub_cached_value == sub_interp_id);
353383
REQUIRE(sub_cached_value != main_interp_id);
384+
385+
py::object sub_dict_type = get_dict_type_object();
386+
py::object sub_ordered_dict_type = get_ordered_dict_type_object();
387+
py::object sub_default_dict_type = get_default_dict_type_object();
388+
389+
// Verify that the subinterpreter has its own cached type objects.
390+
// For static types, they should be the same object across interpreters.
391+
// See also: https://docs.python.org/3/c-api/typeobj.html#static-types
392+
REQUIRE(sub_dict_type.is(dict_type)); // dict is a static type
393+
REQUIRE(sub_ordered_dict_type.is(ordered_dict_type)); // OrderedDict is a static type
394+
// For heap types, they are dynamically created per-interpreter.
395+
// See also: https://docs.python.org/3/c-api/typeobj.html#heap-types
396+
REQUIRE_FALSE(sub_default_dict_type.is(default_dict_type)); // defaultdict is a heap type
397+
398+
// Set up a weakref callback to detect when the subinterpreter's cached default_dict_type
399+
// is destroyed so the gil_safe_call_once_and_store storage is not leaked when the
400+
// subinterpreter is shutdown.
401+
(void) py::weakref(sub_default_dict_type,
402+
py::cpp_function([&](py::handle weakref) -> void {
403+
sub_default_dict_type_destroyed = true;
404+
weakref.dec_ref();
405+
}))
406+
.release();
354407
}
355408

356409
// Back in main interpreter, verify main's value is unchanged
357410
auto &main_value_after = storage.get_stored();
358411
REQUIRE(main_value_after.cast<py::interpid_t>() == main_interp_id);
359412

413+
// Verify that the types cached in main are unchanged
414+
py::object dict_type_after = get_dict_type_object();
415+
py::object ordered_dict_type_after = get_ordered_dict_type_object();
416+
py::object default_dict_type_after = get_default_dict_type_object();
417+
REQUIRE(dict_type_after.is(dict_type));
418+
REQUIRE(ordered_dict_type_after.is(ordered_dict_type));
419+
REQUIRE(default_dict_type_after.is(default_dict_type));
420+
421+
// Verify that the subinterpreter's cached default_dict_type was destroyed
422+
REQUIRE(sub_default_dict_type_destroyed);
423+
360424
unsafe_reset_internals_for_single_interpreter();
361425
}
362426

0 commit comments

Comments
 (0)