Skip to content

Commit 3a73a52

Browse files
gh-157443: Track the argument of sys.unraisablehook in the GC
PyStructSequence_New() does not track the created object, and the argument of sys.unraisablehook holds the exception and its traceback, so a reference cycle through it was never collected. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent da5a155 commit 3a73a52

3 files changed

Lines changed: 27 additions & 0 deletions

File tree

‎Lib/test/test_sys.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,27 @@ def hook_func(args):
15151515
expected = None
15161516
hook_args = None
15171517

1518+
def test_unraisablehook_args_reference_cycle(self):
1519+
# gh-157443: The hook argument can be a part of a reference cycle
1520+
# via the traceback, which must be collected by the GC.
1521+
import weakref
1522+
class Holder:
1523+
pass
1524+
def f():
1525+
holder = Holder()
1526+
def hook(unraisable):
1527+
holder.unraisable = unraisable
1528+
with test.support.swap_attr(sys, 'unraisablehook', hook):
1529+
class C:
1530+
def __del__(self):
1531+
raise ValueError
1532+
C()
1533+
self.assertTrue(gc.is_tracked(holder.unraisable))
1534+
return weakref.ref(holder)
1535+
wr = f()
1536+
support.gc_collect()
1537+
self.assertIsNone(wr())
1538+
15181539
def test_custom_unraisablehook_fail(self):
15191540
_testcapi = import_helper.import_module('_testcapi')
15201541
from _testcapi import err_writeunraisable
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The argument of :func:`sys.unraisablehook` is now tracked by the garbage
2+
collector, so that a reference cycle through it (for example via the
3+
traceback) can be collected.

‎Python/errors.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,9 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type,
14471447
Py_DECREF(args);
14481448
return NULL;
14491449
}
1450+
// PyStructSequence_New() does not track the object, and it can be
1451+
// a part of a reference cycle via the traceback.
1452+
PyObject_GC_Track(args);
14501453
return args;
14511454
}
14521455

0 commit comments

Comments
 (0)