Skip to content

BUG: Fix reference leakage for output arrays in reduction functions #29358

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

Merged
merged 8 commits into from
Jul 11, 2025

Conversation

MaanasArora
Copy link
Contributor

@MaanasArora MaanasArora commented Jul 11, 2025

Fixes #29355.

We already increment a reference to out in _set_out_array (in ufunc_object.c):

c
static int
_set_out_array(PyObject *obj, PyArrayObject **store)
{
    ...
    Py_INCREF(obj);
    *store = (PyArrayObject *)obj;

    return 0;
    ...
}

This PR removes additional increments and adds a missing decrement that caused the memory leakage.

@VinothkumarS79
Copy link

static int
_set_out_array(PyObject *obj, PyArrayObject **store)
{
if (*store != NULL) {
Py_DECREF(*store); // Release the previous reference
}
Py_INCREF(obj);
*store = (PyArrayObject *)obj;

return 0;

}

If there's any chance obj isn't guaranteed to be a PyArrayObject, you might want to add:

if (!PyArray_Check(obj)) {
PyErr_SetString(PyExc_TypeError, "Expected a NumPy array");
return -1;
}

@@ -3035,9 +3032,6 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind,
return NULL;
}

/* Take a reference to out for later returning */
Py_XINCREF(out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I would prefer to keep the incref (somewhere in the function).

Removing the XINCREF here is correct and actually convenient! But it means the function takes one reference to out (if passed) and returns one new reference. Returning a new reference is obvious, but that means you have +1-1, i.e. the -1 is "stealing" the original reference.

But reference count stealing tends to be a bit confusing, largely on account it not very being common, so I would prefer to avoid introducing it (unless it is an **out API, where out is mutated/replaced in-place).
Also with alternative Python implementations and free-threading, reference stealing is a pattern that often needs to be avoided.


That is, re-add a decref in the calling code instead (I/we probably lost it when working on array_wrap improvements).
(Or never take a reference, since out= is currently always an ndarray already.)

We could add an explicit test as well via sys.getrefcount() (needs disabling on PyPy), almost surprised we don't have one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to do almost the same as here, is to use:

Py_XSETREF(out, function(..., out, ...));

at the call-site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointers / context! That makes sense. I've reverted the reference stealing and instead readjusted some of the incref code to be a bit clearer (hopefully) and added a decref to PyUFunc_GenericReduction. Adding a test to check the reference counts!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reverted to using XINCREF--we need the reference for the promotion! (and also seems clearer, actually). Also added a test.

@seberg seberg added the 09 - Backport-Candidate PRs tagged should be backported label Jul 11, 2025
Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, two minor fixes needed.


out = np.empty(arr.shape, dtype=np.int32)
np.add.accumulate(arr, dtype=np.int32, out=out)
assert count == sys.getrefcount(arr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to check the refcount on out (also)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@@ -3727,6 +3727,8 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc,
if (ret == NULL) {
goto fail;
}

Py_XDECREF(out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, but also needed on error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix and good new tests!

@seberg seberg merged commit 82bd6ef into numpy:main Jul 11, 2025
75 checks passed
charris pushed a commit to charris/numpy that referenced this pull request Jul 22, 2025
…umpy#29358)

Add back missing DECREF for `out=` in ufunc.reduce (and etc.) when `out` is passed.

* BUG: add Py_XDECREF to `out` for failure case in generic reduction

* TST: add reference count checks for `out` in reduction leak tests
@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Jul 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: Memory leak using out= parameter in numpy version 2.3
4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy