@@ -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+
4367TEST_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