https://github.com/python/cpython/commit/f6842ddae6b0c744b89c33b9bb088d7c4b3eb8d3
commit: f6842ddae6b0c744b89c33b9bb088d7c4b3eb8d3
branch: 3.14
author: Donghee Na <[email protected]>
committer: corona10 <[email protected]>
date: 2026-09-01T23:36:06Z
summary:
[3.14] gh-156371: Fix missing PyRefTracer_DESTROY events for trashcan objects
(gh-156372) (gh-156802)
(cherry picked from commit 3dfa5745f47370e86f075d356da1f4519e3077ac)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst
M Lib/test/test_capi/test_object.py
M Modules/_testcapimodule.c
M Objects/object.c
diff --git a/Lib/test/test_capi/test_object.py
b/Lib/test/test_capi/test_object.py
index 0b188fc90597744..e2345635c473257 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -260,5 +260,21 @@ def func(x):
for i in [0, 1, 2]:
self.assertFalse(_testcapi.pyobject_is_unique_temporary_new_object())
+
+class RefTracerTest(unittest.TestCase):
+ @support.requires_resource('cpu')
+ @support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
+ def test_destroy_traced_for_trashcan_deferred_objects(self):
+ depth = 200_000
+ chain = None
+ for _ in range(depth):
+ chain = [chain]
+ _testcapi.start_counting_list_destroys()
+ del chain
+ destroys = _testcapi.stop_counting_list_destroys()
+ self.assertEqual(destroys, depth)
+
+
if __name__ == "__main__":
unittest.main()
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst
new file mode 100644
index 000000000000000..31ebff6f247f7e3
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-01-08-38.gh-issue-156371.USpwKG.rst
@@ -0,0 +1,2 @@
+Fix missing ``PyRefTracer_DESTROY`` events for trashcan deferred objects.
+Patch by Donghee Na.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index d50838e876f24ae..ee6ab7ab2c68771 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -39,6 +39,7 @@ static struct PyModuleDef _testcapimodule;
// Module state
typedef struct {
PyObject *error; // _testcapi.error object
+ Py_ssize_t list_destroys;
} testcapistate_t;
static testcapistate_t*
@@ -2410,6 +2411,36 @@ test_reftracer(PyObject *ob, PyObject
*Py_UNUSED(ignored))
return NULL;
}
+static int
+_listdestroytracer(PyObject *obj, PyRefTracerEvent event, void *data)
+{
+ if (event == PyRefTracer_DESTROY && PyList_CheckExact(obj)) {
+ _Py_atomic_add_ssize((Py_ssize_t *)data, 1);
+ }
+ return 0;
+}
+
+static PyObject *
+start_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ testcapistate_t *state = get_testcapi_state(self);
+ _Py_atomic_store_ssize(&state->list_destroys, 0);
+ if (PyRefTracer_SetTracer(_listdestroytracer, &state->list_destroys) != 0)
{
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+stop_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ if (PyRefTracer_SetTracer(NULL, NULL) != 0) {
+ return NULL;
+ }
+ return PyLong_FromSsize_t(
+ _Py_atomic_load_ssize(&get_testcapi_state(self)->list_destroys));
+}
+
static PyObject *
function_set_warning(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
@@ -2614,6 +2645,8 @@ static PyMethodDef TestMethods[] = {
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
{"test_buildvalue_p", test_buildvalue_p, METH_NOARGS},
{"test_reftracer", test_reftracer, METH_NOARGS},
+ {"start_counting_list_destroys", start_counting_list_destroys,
METH_NOARGS},
+ {"stop_counting_list_destroys", stop_counting_list_destroys, METH_NOARGS},
{"_test_thread_state", test_thread_state, METH_VARARGS},
{"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS},
#ifndef MS_WINDOWS
diff --git a/Objects/object.c b/Objects/object.c
index 3a104bcd932cd4f..b17a5bd70caf91d 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -3120,6 +3120,10 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
* up distorting allocation statistics.
*/
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
+#ifdef Py_TRACE_REFS
+ _Py_ForgetReference(op);
+#endif
+ _PyReftracerTrack(op, PyRefTracer_DESTROY);
(*dealloc)(op);
}
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]