Skip to content

Function args sometimes not decref:ed after return #144165

@mandolaerik

Description

@mandolaerik

Bug report

Bug description:

I have an application written in C and Python, which implements threading using pthreads for performance-critical C code, and occasionally runs python code from these threads, protected by the GIL. I have somewhat complex reference counting logic which I unit test using weakref.Ref. These tests work flawlessly in 3.13 and earlier, but started to break in 3.14 (specifically 3.14.2, compiled with free threading disabled, linux/SLES12). My test looks something like:

from weakref import Ref
import gc
# my_c_module is implemented using the Python C API
from my_c_module import call_in_thread, f

class Data: pass

data = Data()
ref = Ref(data)
def g():
    f(data)

call_in_thread(g)

assert ref() is not None
del data
gc.collect()
assert ref() is None

By inspecting reference counts it seems that data sometimes gets an extra reference while returning from f: Py_REFCNT always returns 4 in the end of f, but right after returning into g, sys.getrefcount returns either 3 or 4. With 3.14 the test fails maybe 75% of the time, and with 3.10..3.13 it has never failed over thousands of runs. Unfortunately, I have not yet been able to reproduce it in a toy environment.

Playing around a bit, it seems to only happen when the variable is referenced from different pthreads, and only when a value is passed as an argument to a function implemented in C.

I also dug a bit with GDB, seems like the difference between the failing and succeeding path is this code path in _Py_Dealloc, which seems to defer deallocation of the args tuple, thus inhibiting arg decref:

    if (margin < 2 && gc_flag) {
        _PyTrash_thread_deposit_object(tstate, (PyObject *)op);
        return;
    }

It seems that these objects are leaked forever; that tuple object seems to remain even after I do things like call_in_thread(lambda: gc.collect()).

Any ideas? In particular, do you have any ideas on how to create a reproducer I can share? (the full application is in part proprietary, so cannot share that)

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions