diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 499ba77fd19542d..0426d4fc4e20280 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -10,6 +10,7 @@ import threading import time import random +import textwrap from test import support from test.support import script_helper, ALWAYS_EQ @@ -1009,6 +1010,33 @@ def __del__(self): pass del x support.gc_collect() + @support.cpython_only + def test_no_memory_when_clearing(self): + # gh-118331: Make sure we do not raise an exception from the destructor + # when clearing weakrefs if allocating the intermediate tuple fails. + code = textwrap.dedent(""" + import _testcapi + import weakref + + class Obj: + pass + + def callback(obj): + pass + + + obj = Obj() + # The choice of 50 is arbitrary, but must be large enough to ensure + # the allocation won't be serviced by the free list. + wrs = [weakref.ref(obj, callback) for _ in range(50)] + _testcapi.set_nomemory(0) + del obj + print("hi matt") + """).strip() + res = script_helper.assert_python_failure("-c", code) + stderr = res.err.decode("ascii", "backslashreplace") + self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type") + class SubclassableWeakrefTestCase(TestBase): diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 206107e8505dc73..7ee8a8e5a59fbc0 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1016,7 +1016,13 @@ PyObject_ClearWeakRefs(PyObject *object) PyObject *exc = PyErr_GetRaisedException(); PyObject *tuple = PyTuple_New(num_weakrefs * 2); if (tuple == NULL) { - _PyErr_ChainExceptions1(exc); + _PyWeakref_ClearWeakRefsExceptCallbacks(object); + if (exc == NULL) { + PyErr_WriteUnraisable(object); + } + else { + _PyErr_ChainExceptions1(exc); + } return; }