Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Traceback reference leak in CPython 3.12.0a6 and above #5724

Closed
yut23 opened this issue Sep 25, 2023 · 2 comments · Fixed by #5725
Closed

[BUG] Traceback reference leak in CPython 3.12.0a6 and above #5724

yut23 opened this issue Sep 25, 2023 · 2 comments · Fixed by #5725

Comments

@yut23
Copy link
Contributor

yut23 commented Sep 25, 2023

Describe the bug

In CPython 3.12.0a6 and newer with CYTHON_FAST_THREAD_STATE=1, traceback objects for Python code called from Cython have their refcounts incremented but never decremented. This is triggered when Cython code calls into a Python function that then raises an exception. The source of the leak is __Pyx_ErrRestoreInState(), which never calls Py_XDECREF on tb:

static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) {
#if PY_VERSION_HEX >= 0x030C00A6
PyObject *tmp_value;
assert(type == NULL || (value != NULL && type == (PyObject*) Py_TYPE(value)));
CYTHON_UNUSED_VAR(type);
if (value) {
#if CYTHON_COMPILING_IN_CPYTHON
if (unlikely(((PyBaseExceptionObject*) value)->traceback != tb))
#endif
// If this fails, we may lose the traceback but still set the expected exception below.
PyException_SetTraceback(value, tb);
}
tmp_value = tstate->current_exception;
tstate->current_exception = value;
Py_XDECREF(tmp_value);
#else

Code to reproduce the behaviour:

lib.pyx:

def get_spam(data):
    # this will raise a KeyError in the Python code for UserDict.__getitem__()
    return data["spam"]

repro.py:

from collections import UserDict
from lib import get_spam

def main():
    d = UserDict()
    d["lots of data"] = bytearray(1024**2)
    try:
        get_spam(d)
    except KeyError:
        pass

for i in range(10):
    main()

I've uploaded a full reproducer to https://github.com/yut23/cython_3.12_traceback_leak, along with some code I used to track down the leaks.

Expected behaviour

No response

OS

Linux, macOS

Python version

3.12.0a6+

Cython version

3.0.0+

Additional context

We first encountered this when running yt's test suite on Python 3.12, and reported it to CPython in python/cpython#109602. Thanks to @neutrinoceros, @Xarthisius, and @mdboom for their work in simplifying the reproducer!

I'll make a PR with a proposed fix shortly.

@ThomasWaldmann
Copy link

@scoder Does this issue also apply to Cython 0.29.x and if so, was a fix already released (could not find it in changelog)?

@scoder
Copy link
Contributor

scoder commented Mar 2, 2024

The issue milestone says 3.0.3, so the fix was (probably) not backported. Looks more like a 3.0.x issue to me. There were a lot of changes in the exception handling code since 0.29.x. It doesn't look like 0.29.x has any special casing code for Py3.12 here.

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

Successfully merging a pull request may close this issue.

3 participants