Function args sometimes not decref:ed after return · Issue #144165 · python/cpython · GitHub
Skip to content

Function args sometimes not decref:ed after return #144165

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedtopic-C-APItype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions