diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 4c7c900eb56ae1..c74c470aaa8693 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1044,6 +1044,50 @@ def callback(obj): stderr = res.err.decode("ascii", "backslashreplace") self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'") + def test_module_at_shutdown(self): + # gh-132413: Weakref gets cleared by gc before modules are finalized + code = textwrap.dedent(""" + import os # any module other than sys + import weakref + wr = weakref.ref(os) + + def gen(): + try: + yield + finally: + print( + os is not None, + os is wr() + ) + + it = gen() + next(it) + print('fini', end=' ') + """) + with self.subTest("gen"): + res = script_helper.assert_python_ok('-c', code) + self.assertEqual(res.out.rstrip(), b'fini True True') + + code = textwrap.dedent(""" + import os + import weakref + wr = weakref.ref(os) + + class C: + def __del__(self): + print( + os is not None, + os is wr() + ) + + l = [C()] + l.append(l) + print('fini', end=' ') + """) + with self.subTest("del"): + res = script_helper.assert_python_ok('-c', code) + self.assertEqual(res.out.rstrip(), b'fini True True') + class SubclassableWeakrefTestCase(TestBase): diff --git a/Misc/NEWS.d/next/Library/2025-06-30-15-12-33.gh-issue-132413.TvpIn2.rst b/Misc/NEWS.d/next/Library/2025-06-30-15-12-33.gh-issue-132413.TvpIn2.rst new file mode 100644 index 00000000000000..3e778bf54f8e02 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-30-15-12-33.gh-issue-132413.TvpIn2.rst @@ -0,0 +1 @@ +Fix crash in C version of :mod:`datetime` when used during interpreter shutdown. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 724fda63511282..7fe5e96c70a1af 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1546,6 +1546,13 @@ static PyObject* finalize_remove_modules(PyObject *modules, int verbose) { PyObject *weaklist = PyList_New(0); + PyObject *weak_ext = NULL; // for extending the weaklist + if (weaklist != NULL) { + weak_ext = PyList_New(0); + if (weak_ext == NULL) { + Py_CLEAR(weaklist); + } + } if (weaklist == NULL) { PyErr_FormatUnraisable("Exception ignored while removing modules"); } @@ -1554,8 +1561,22 @@ finalize_remove_modules(PyObject *modules, int verbose) if (weaklist != NULL) { \ PyObject *wr = PyWeakref_NewRef(mod, NULL); \ if (wr) { \ - PyObject *tup = PyTuple_Pack(2, name, wr); \ - if (!tup || PyList_Append(weaklist, tup) < 0) { \ + PyObject *list; \ + PyObject *tup; \ + if (Py_REFCNT(wr) > 1) { \ + /* gh-132413: When the weakref is already used elsewhere, + * finalize_modules_clear_weaklist() rather than the GC + * should clear the referenced module since the GC tries + * to clear the wrakref first. The weaklist requires the + * order in which such modules are cleared first. */ \ + tup = PyTuple_Pack(3, name, wr, mod); \ + list = weak_ext; \ + } \ + else { \ + tup = PyTuple_Pack(2, name, wr); \ + list = weaklist; \ + } \ + if (!tup || PyList_Append(list, tup) < 0) { \ PyErr_FormatUnraisable("Exception ignored while removing modules"); \ } \ Py_XDECREF(tup); \ @@ -1607,6 +1628,14 @@ finalize_remove_modules(PyObject *modules, int verbose) Py_DECREF(iterator); } } + + if (weaklist != NULL) { + if (PyList_Extend(weaklist, weak_ext) < 0) { + PyErr_FormatUnraisable("Exception ignored while removing modules"); + } + Py_DECREF(weak_ext); + } + #undef CLEAR_MODULE #undef STORE_MODULE_WEAKREF