@@ -301,6 +301,8 @@ TEST_CASE("Restart the interpreter") {
301301 py::module_::import (" __main__" ).attr (" internals_destroy_test" )
302302 = py::capsule (&ran, [](void *ran) {
303303 py::detail::get_internals ();
304+ REQUIRE (has_state_dict_internals_obj ());
305+ REQUIRE (has_pybind11_internals_static ());
304306 *static_cast <bool *>(ran) = true ;
305307 });
306308 REQUIRE_FALSE (has_state_dict_internals_obj ());
@@ -384,6 +386,210 @@ TEST_CASE("Subinterpreter") {
384386 REQUIRE (has_state_dict_internals_obj ());
385387}
386388
389+ #if defined(PYBIND11_SUBINTERPRETER_SUPPORT)
390+ TEST_CASE (" Multiple Subinterpreters" ) {
391+ // Make sure the module is in the main interpreter and save its pointer
392+ auto *main_ext = py::module_::import (" external_module" ).ptr ();
393+ auto main_int
394+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
395+ py::module_::import (" external_module" ).attr (" multi_interp" ) = " 1" ;
396+
397+ auto *main_tstate = PyThreadState_Get ();
398+
399+ // / Create and switch to a subinterpreter.
400+ auto *sub1_tstate = Py_NewInterpreter ();
401+ py::detail::get_interpreter_count ()++;
402+
403+ py::list (py::module_::import (" sys" ).attr (" path" )).append (py::str (" ." ));
404+
405+ // The subinterpreter has its own copy of this module which is completely separate from main
406+ auto *sub1_ext = py::module_::import (" external_module" ).ptr ();
407+ REQUIRE (sub1_ext != main_ext);
408+ REQUIRE_FALSE (py::hasattr (py::module_::import (" external_module" ), " multi_interp" ));
409+ py::module_::import (" external_module" ).attr (" multi_interp" ) = " 2" ;
410+ // The sub-interpreter also has its own internals
411+ auto sub1_int
412+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
413+ REQUIRE (sub1_int != main_int);
414+
415+ // Create another interpreter
416+ auto *sub2_tstate = Py_NewInterpreter ();
417+ py::detail::get_interpreter_count ()++;
418+
419+ py::list (py::module_::import (" sys" ).attr (" path" )).append (py::str (" ." ));
420+
421+ // The second subinterpreter is separate from both main and the other sub-interpreter
422+ auto *sub2_ext = py::module_::import (" external_module" ).ptr ();
423+ REQUIRE (sub2_ext != main_ext);
424+ REQUIRE (sub2_ext != sub1_ext);
425+ REQUIRE_FALSE (py::hasattr (py::module_::import (" external_module" ), " multi_interp" ));
426+ py::module_::import (" external_module" ).attr (" multi_interp" ) = " 3" ;
427+ // The sub-interpreter also has its own internals
428+ auto sub2_int
429+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
430+ REQUIRE (sub2_int != main_int);
431+ REQUIRE (sub2_int != sub1_int);
432+
433+ PyThreadState_Swap (sub1_tstate); // go back to sub1
434+
435+ REQUIRE (py::cast<std::string>(py::module_::import (" external_module" ).attr (" multi_interp" ))
436+ == " 2" );
437+
438+ PyThreadState_Swap (main_tstate); // go back to main
439+
440+ auto post_int
441+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
442+ // Make sure internals went back the way it was before
443+ REQUIRE (main_int == post_int);
444+
445+ REQUIRE (py::cast<std::string>(py::module_::import (" external_module" ).attr (" multi_interp" ))
446+ == " 1" );
447+
448+ PyThreadState_Swap (sub1_tstate);
449+ Py_EndInterpreter (sub1_tstate);
450+ PyThreadState_Swap (sub2_tstate);
451+ Py_EndInterpreter (sub2_tstate);
452+
453+ py::detail::get_interpreter_count () = 1 ;
454+ PyThreadState_Swap (main_tstate);
455+ }
456+ #endif
457+
458+ #if defined(Py_MOD_PER_INTERPRETER_GIL_SUPPORTED) && defined(PYBIND11_SUBINTERPRETER_SUPPORT)
459+ TEST_CASE (" Per-Subinterpreter GIL" ) {
460+ auto main_int
461+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
462+
463+ std::atomic<int > started = 0 , finished = 0 , failure = 0 , sync = 0 ;
464+
465+ // REQUIRE throws on failure, so we can't use it within the thread
466+ # define T_REQUIRE (status ) \
467+ do { \
468+ assert (status); \
469+ if (!(status)) \
470+ ++failure; \
471+ } while (0 )
472+
473+ auto &&thread_main = [&](int num) {
474+ while (started == 0 )
475+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
476+ ++started;
477+
478+ py::gil_scoped_acquire gil;
479+ auto main_tstate = PyThreadState_Get ();
480+
481+ // we have the GIL, we can access the main interpreter
482+ auto t_int
483+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
484+ T_REQUIRE (t_int == main_int);
485+ py::module_::import (" external_module" ).attr (" multi_interp" ) = " 1" ;
486+
487+ PyInterpreterConfig cfg = _PyInterpreterConfig_INIT;
488+ PyThreadState *sub = nullptr ;
489+ auto status = Py_NewInterpreterFromConfig (&sub, &cfg);
490+ T_REQUIRE (!PyStatus_IsError (status));
491+
492+ py::detail::get_interpreter_count ()++;
493+
494+ py::list (py::module_::import (" sys" ).attr (" path" )).append (py::str (" ." ));
495+
496+ // we have switched to the new interpreter and released the main gil
497+
498+ // widget_module did not provide the mod_per_interpreter_gil tag, so it cannot be imported
499+ bool caught = false ;
500+ try {
501+ py::module_::import (" widget_module" );
502+ } catch (pybind11::error_already_set &pe) {
503+ T_REQUIRE (pe.matches (PyExc_ImportError));
504+ std::string msg (pe.what ());
505+ T_REQUIRE (msg.find (" does not support loading in subinterpreters" )
506+ != std::string::npos);
507+ caught = true ;
508+ }
509+ T_REQUIRE (caught);
510+
511+ T_REQUIRE (!py::hasattr (py::module_::import (" external_module" ), " multi_interp" ));
512+ py::module_::import (" external_module" ).attr (" multi_interp" ) = std::to_string (num);
513+
514+ // wait for something to set sync to our thread number
515+ // we are holding our subinterpreter's GIL
516+ while (sync != num)
517+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
518+
519+ // now change it so the next thread can mvoe on
520+ ++sync;
521+
522+ // but keep holding the GIL until after the next thread moves on as well
523+ while (sync == num + 1 )
524+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
525+
526+ // one last check before quitting the thread, the internals should be different
527+ auto sub_int
528+ = py::module_::import (" external_module" ).attr (" internals_at" )().cast <uintptr_t >();
529+ T_REQUIRE (sub_int != main_int);
530+
531+ Py_EndInterpreter (sub);
532+
533+ ++finished;
534+
535+ PyThreadState_Swap (
536+ main_tstate); // switch back so the scoped_acquire can release the GIL properly
537+ };
538+
539+ std::thread (thread_main, 1 ).detach ();
540+ std::thread (thread_main, 2 ).detach ();
541+
542+ // we spawned two threads, at this point they are both waiting for started to increase
543+ ++started;
544+
545+ // ok now wait for the threads to start
546+ while (started != 3 )
547+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
548+
549+ // we still hold the main GIL, at this point both threads are waiting on the main GIL
550+ // IN THE CASE of free threading, the threads are waiting on sync (because there is no GIL)
551+
552+ // IF the below code hangs in one of the wait loops, then the child thread GIL behavior did not
553+ // function as expected.
554+ {
555+ // release the GIL and allow the threads to run
556+ py::gil_scoped_release nogil;
557+
558+ // the threads are now waiting on the sync
559+ REQUIRE (sync == 0 );
560+
561+ // this will trigger thread 1 and then advance and trigger 2 and then advance
562+ sync = 1 ;
563+
564+ // wait for thread 2 to advance
565+ while (sync != 3 )
566+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
567+
568+ // we know now that thread 1 has run and may be finishing
569+ // and thread 2 is waiting for permission to advance
570+
571+ // so we move sync so that thread 2 can finish executing
572+ ++sync;
573+
574+ ++finished;
575+
576+ // now wait for both threads to complete
577+ while (finished != 3 )
578+ std::this_thread::sleep_for (std::chrono::microseconds (1 ));
579+ }
580+
581+ // now we have the gil again, sanity check
582+ REQUIRE (py::cast<std::string>(py::module_::import (" external_module" ).attr (" multi_interp" ))
583+ == " 1" );
584+
585+ // the threads are stopped. we can now lower this for the rest of the test
586+ py::detail::get_interpreter_count () = 1 ;
587+
588+ // make sure nothing unexpected happened inside the threads, now that they are completed
589+ REQUIRE (failure == 0 );
590+ }
591+ #endif
592+
387593TEST_CASE (" Execution frame" ) {
388594 // When the interpreter is embedded, there is no execution frame, but `py::exec`
389595 // should still function by using reasonable globals: `__main__.__dict__`.
0 commit comments