diff --git a/Lib/test/test_free_threading/test_interpreters.py b/Lib/test/test_free_threading/test_interpreters.py new file mode 100644 index 00000000000000..a9a432fae16b1e --- /dev/null +++ b/Lib/test/test_free_threading/test_interpreters.py @@ -0,0 +1,29 @@ +import textwrap +import unittest + +from test.support import import_helper, script_helper + + +# Make sure _testinternalcapi is available before running the test. +import_helper.import_module('_testinternalcapi') + + +class InterpreterTeardownTests(unittest.TestCase): + def test_destroy_subinterpreter_does_not_abort(self): + # gh-153176: destroy_interpreter(basic=True) used to call + # PyThreadState_Clear() on a non-current thread state, which on a + # free-threaded debug build reclaimed mimalloc pages into a heap not + # owned by the current thread and aborted the process. Run the + # reproduction in a subprocess so that a regression surfaces as a + # non-zero exit / SIGABRT instead of killing the test runner. + script = textwrap.dedent(""" + import _testinternalcapi + + interpid = _testinternalcapi.create_interpreter() + _testinternalcapi.destroy_interpreter(interpid, basic=True) + """) + script_helper.assert_python_ok('-c', script) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index d0d1f1f1bc8e53..28775d9ecd332f 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2301,8 +2301,7 @@ destroy_interpreter(PyObject *self, PyObject *args, PyObject *kwargs) } t2 = PyThreadState_New(interp); prev = PyThreadState_Swap(t2); - PyThreadState_Clear(t1); - PyThreadState_Delete(t1); + // t1 is deliberately left alive; Py_EndInterpreter() must clean it up. Py_EndInterpreter(t2); PyThreadState_Swap(prev); }