Skip to content
Closed
44 changes: 44 additions & 0 deletions Lib/test/test_weakref.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash in C version of :mod:`datetime` when used during interpreter shutdown.
33 changes: 31 additions & 2 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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); \
Expand Down Expand Up @@ -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

Expand Down
Loading