@@ -301,6 +301,8 @@ TEST_CASE("Restart the interpreter") {
301
301
py::module_::import (" __main__" ).attr (" internals_destroy_test" )
302
302
= py::capsule (&ran, [](void *ran) {
303
303
py::detail::get_internals ();
304
+ REQUIRE (has_state_dict_internals_obj ());
305
+ REQUIRE (has_pybind11_internals_static ());
304
306
*static_cast <bool *>(ran) = true ;
305
307
});
306
308
REQUIRE_FALSE (has_state_dict_internals_obj ());
@@ -384,6 +386,210 @@ TEST_CASE("Subinterpreter") {
384
386
REQUIRE (has_state_dict_internals_obj ());
385
387
}
386
388
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
+
387
593
TEST_CASE (" Execution frame" ) {
388
594
// When the interpreter is embedded, there is no execution frame, but `py::exec`
389
595
// should still function by using reasonable globals: `__main__.__dict__`.
0 commit comments