diff --git a/Lib/test/support/singlephase_helper.py b/Lib/test/support/singlephase_helper.py new file mode 100644 index 00000000000000..7f827237cd8c8b --- /dev/null +++ b/Lib/test/support/singlephase_helper.py @@ -0,0 +1,32 @@ +"""Helper module for _testembed.c test_subinterpreter_finalize test. +""" + +import sys + +PREFIX = "shared_string_" +SUFFIX = "DByGMRJRSEDp29PkiZQNHA" + +def init_sub1(): + import _testsinglephase + # Create global object to be shared when imported a second time. + _testsinglephase._shared_list = [] + # Create a new interned string, to be shared with the main interpreter. + _testsinglephase._shared_string = sys.intern(PREFIX + SUFFIX) + + +def init_sub2(): + # This sub-interpreter will share a reference to _shared_list with the + # first interpreter, since importing _testsinglephase will not initialize + # the module a second time but will just copy the global dict. This + # situtation used to trigger a bug like gh-125286 if TraceRefs was enabled + # for the build. + import _testsinglephase + + +def init_main(): + global shared_str + # The first sub-interpreter has already interned this string value. The + # return value from intern() will be the same string object created in + # sub-interpreter 1. Assign it to a global so it lives until the main + # interpreter is shutdown. + shared_string = sys.intern(PREFIX + SUFFIX) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index ab619e32429d63..e4c385720de6f2 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2072,6 +2072,46 @@ static void configure_init_main(PyConfig *config) } +static int test_subinterpreter_finalize(void) +{ + // This test is done by creating two subinterpreters and then sharing + // objects between them by importing a basic single-phase init extension + // (m_size == -1). Then we finalize the interpreters in the reverse order + // so that the interpreter that created the shared objects gets finalized + // first. + _testembed_Py_InitializeFromConfig(); + + PyThreadState *tstate1 = Py_NewInterpreter(); + PyThreadState_Swap(tstate1); + PyRun_SimpleString( + "import test.support.singlephase_helper\n" + "test.support.singlephase_helper.init_sub1\n" + ); + + PyThreadState *tstate2 = Py_NewInterpreter(); + PyThreadState_Swap(tstate2); + PyRun_SimpleString( + "import test.support.singlephase_helper\n" + "test.support.singlephase_helper.init_sub2\n" + ); + + PyThreadState *main_tstate = _PyRuntime.main_tstate; + PyThreadState_Swap(main_tstate); + PyRun_SimpleString( + "import test.support.singlephase_helper\n" + "test.support.singlephase_helper.init_main\n" + ); + + PyThreadState_Swap(tstate1); + Py_EndInterpreter(tstate1); + PyThreadState_Swap(tstate2); + Py_EndInterpreter(tstate2); + Py_Finalize(); + + return 0; +} + + static int test_init_run_main(void) { PyConfig config; @@ -2480,6 +2520,7 @@ static struct TestCase TestCases[] = { {"test_initconfig_get_api", test_initconfig_get_api}, {"test_initconfig_exit", test_initconfig_exit}, {"test_initconfig_module", test_initconfig_module}, + {"test_subinterpreter_finalize", test_subinterpreter_finalize}, {"test_run_main", test_run_main}, {"test_run_main_loop", test_run_main_loop}, {"test_get_argc_argv", test_get_argc_argv},