diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 93b3382b9c654e..856d47be479444 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -7295,6 +7295,31 @@ def test_update_type_cache(self): """) script_helper.assert_python_ok('-c', script) + @unittest.skipIf(_interpreters is None, "missing _interpreters module") + def test_static_type_concurrent_init_fini(self): + # gh-136421: When a managed static extension type is concurrently used + # by only subinterpreters, there was a crash due to the runtime state + # rather than an interpreter state being updated wrongly by mistaking + # the type's first initialization stage or last finalization one. + script = textwrap.dedent(""" + import threading + import _interpreters + + def run(id): + _interpreters.exec(id, "import _datetime; print('a', end='')") + _interpreters.destroy(id) + + ids = [_interpreters.create() for i in range(10)] + ts = [threading.Thread(target=run, args=(id,)) for id in ids] + for t in ts: + t.start() + for t in ts: + t.join() + """) + _, out, err = script_helper.assert_python_ok('-c', script) + self.assertEqual(out, b'a' * 10) + self.assertEqual(err, b'') + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) diff --git a/Misc/NEWS.d/next/Library/2025-07-13-01-02-31.gh-issue-136421.5HKeoS.rst b/Misc/NEWS.d/next/Library/2025-07-13-01-02-31.gh-issue-136421.5HKeoS.rst new file mode 100644 index 00000000000000..e61114f825a446 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-13-01-02-31.gh-issue-136421.5HKeoS.rst @@ -0,0 +1,2 @@ +Fix crash in :mod:`datetime` when its static types are initialized or +finalized by multiple interpreters concurrently. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 7a6426593d021f..07196ef0667b70 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7335,12 +7335,17 @@ init_static_types(PyInterpreterState *interp, int reloading) if (reloading) { return 0; } - - // `&...` is not a constant expression according to a strict reading - // of C standards. Fill tp_base at run-time rather than statically. - // See https://bugs.python.org/issue40777 - PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; - PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; + if (_Py_IsMainInterpreter(interp)) { + if (PyType_HasFeature(&PyDateTime_DateType, Py_TPFLAGS_READY)) { + // This function was already called from PyInit__datetime() + return 0; + } + // `&...` is not a constant expression according to a strict reading + // of C standards. Fill tp_base at run-time rather than statically. + // See https://bugs.python.org/issue40777 + PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; + PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; + } /* Bases classes must be initialized before subclasses, * so capi_types must have the types in the appropriate order. */ @@ -7564,6 +7569,13 @@ static PyModuleDef datetimemodule = { PyMODINIT_FUNC PyInit__datetime(void) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + // gh-136421: Ensure static types' runtime state gets cleared in shutting + // down the main interpreter rather than subinterpreters for concurrency. + assert(interp != NULL && _Py_IsMainInterpreter(interp)); + if (init_static_types(interp, 0) < 0) { + return NULL; + } return PyModuleDef_Init(&datetimemodule); }