python / python/cpython

PyRefTracer_DESTROY is not called for objects deferred by the trashcan mechanism

Open
#156,371 2 comments 0 reactions 1 assignee View on GitHub

@corona10 is already working on this.

Since Aug 25, 2026.

3.14 3.15 3.16 type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

This works correctly until Python 3.13, but https://github.com/python/cpython/pull/132280 introduces a regression.
A simple PR will fix this issue, and I have already prepared a fix for this issue.

import os
import subprocess
import sys
import sysconfig
import tempfile

CSRC = r"""
#include <Python.h>

static Py_ssize_t list_destroys = 0;

static int
tracer(PyObject *obj, PyRefTracerEvent event, void *data)
{
    if (event == PyRefTracer_DESTROY && PyList_CheckExact(obj)) {
        list_destroys++;
    }
    return 0;
}

static PyObject *
start(PyObject *self, PyObject *args)
{
    list_destroys = 0;
    if (PyRefTracer_SetTracer(tracer, NULL) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

static PyObject *
stop(PyObject *self, PyObject *args)
{
    if (PyRefTracer_SetTracer(NULL, NULL) < 0) {
        return NULL;
    }
    return PyLong_FromSsize_t(list_destroys);
}

static PyMethodDef methods[] = {
    {"start", start, METH_NOARGS, NULL},
    {"stop", stop, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL},
};

static PyModuleDef_Slot slots[] = {
#ifdef Py_GIL_DISABLED
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
    {0, NULL},
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "reftrace_repro",
    .m_size = 0,
    .m_methods = methods,
    .m_slots = slots,
};

PyMODINIT_FUNC
PyInit_reftrace_repro(void)
{
    return PyModuleDef_Init(&module);
}
"""


def build_module():
    tmpdir = tempfile.mkdtemp(prefix="reftrace_repro_")
    src = os.path.join(tmpdir, "reftrace_repro.c")
    with open(src, "w") as f:
        f.write(CSRC)
    ext = os.path.join(tmpdir, "reftrace_repro.so")
    cmd = ["cc", "-O2", "-shared", "-fPIC",
           "-I", sysconfig.get_path("include"),
           "-I", sysconfig.get_path("platinclude"),
           "-o", ext, src]
    if sys.platform == "darwin":
        cmd.insert(3, "-undefined")
        cmd.insert(4, "dynamic_lookup")
    subprocess.run(cmd, check=True)
    sys.path.insert(0, tmpdir)


def run(depth):
    import reftrace_repro

    chain = None
    for _ in range(depth):
        chain = [chain]
    reftrace_repro.start()
    del chain
    destroys = reftrace_repro.stop()
    missing = depth - destroys
    print(f"depth={depth:>7}  DESTROY events={destroys:>7}  "
          f"missing={missing:>5}  [{'BUG' if missing else 'OK'}]")


if __name__ == "__main__":
    print(sys.version)
    build_module()
    run(100)        
    run(200_000)

Status

$ python3.13 repro.py
3.13.11 (main, Dec 17 2025, 20:55:16) [Clang 21.1.4 ]
depth=    100  DESTROY events=    100  missing=    0  [OK]
depth= 200000  DESTROY events= 200000  missing=    0  [OK]

$ python3.14 repro.py
3.14.3 (main, Feb  3 2026, 15:32:20) [Clang 17.0.0 (clang-1700.6.3.2)]
depth=    100  DESTROY events=    100  missing=    0  [OK]
depth= 200000  DESTROY events= 199923  missing=   77  [BUG]

$ python3.15 repro.py
3.15.0a3 (main, Dec 17 2025, 21:18:28) [Clang 21.1.4 ]
depth=    100  DESTROY events=    100  missing=    0  [OK]
depth= 200000  DESTROY events= 199570  missing=  430  [BUG]

$ ./python.exe repro.py   # main (unpatched)
3.16.0a0 (heads/main:e4b22adf249, Aug 26 2026, 01:02:19) [Clang 21.0.0]
depth=    100  DESTROY events=    100  missing=    0  [OK]
depth= 200000  DESTROY events= 199923  missing=   77  [BUG]
Linked PRs
  • gh-156372
  • gh-156789
  • gh-156802

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.