8000 BUG: dtype refcount cleanups by mattip · Pull Request #14586 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

BUG: dtype refcount cleanups #14586

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 4 commits into from
Oct 3, 2019
Merged

BUG: dtype refcount cleanups #14586

merged 4 commits into from
Oct 3, 2019

Conversation

mattip
Copy link
Member
@mattip mattip commented Sep 24, 2019

gh-13605 did not handle refcounts of dtype instances. I discovered this by running

python runtests.py -t numpy/core/tests/test_deprecations.py

which printed the following twice at the end

*** Reference count error detected: 
an attempt was made to deallocate 12 (d) ***

The lack of those reference count errors when running all the tests (before this PR) would suggest we have places in the code that leak references by incref-ing without decref-ing.

{
Py_DECREF(dtype);
}
return ret;
Copy link
Member Author

Choose a reason for hiding this comment

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

this was one of the places that produced the error message, and was straightforward to find

Copy link
Member

Choose a reason for hiding this comment

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

PyArray_DescrFromType looks weird, it returns a new reference for strings, returns a borrowed reference otherwise.

Copy link
Member

Choose a reason for hiding this comment

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

That function basically knows that some dtypes are immutable, while flexible dtypes are sometimes "adapted" so they need to be copied...

Copy link
Member

Choose a reason for hiding this comment

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

Wait, it returns a borrowed reference? I thought it always increfs, now I am confused :), have to look closer at it later...

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry. I keep confusing PyArray_NewFromDescr and this one (if you want to be clean, add the NULL check). Isn't it enough to replace this with NewFromDescr (which practically never fails, except for flexible types)? The cleanup should not be necessary, since FromString has to steal the reference.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry: PyArray_DescrNewFromType

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

if (ret == NULL)
{
Py_DECREF(descr);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this fix came from reviewing the use of PyArray_DescrFrom* in the C file, and did not actually leak (yet).

Copy link
Member
@seberg seberg Sep 25, 2019

Choose a reason for hiding this comment

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

Wow, same bug yeah. Same thing, just make it PyArray_DescrNewFromType. With NULL check if you wish.

EDIT: To be clear, the ret == NULL check is not correct here.

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

Py_DECREF(dtype);
if (r == NULL) {
Py_DECREF(dtype);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this was the source of the other error report, and is a bit subtle: if r is an ndarray we do not need to decref

Copy link
Member

Choose a reason for hiding this comment

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

Looks like r === NULL case is already handled up above. What am I missing?

Copy link
Member

Choose a reason for hiding this comment

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

The DECREF can be done unconditioanlly here, I agree. I do not think that path can be taken at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

@mattip
Copy link
Member Author
mattip commented Sep 24, 2019

Reminder to self: use python runtest.py --warn-error


string = PyBytes_AsString(byte_obj);
if (string == NULL) {
return NULL;
}

return PyArray_FromString(
string, -1, PyArray_DescrFromType(NPY_FLOAT64), -1, " ");
Copy link
Member

Choose a reason for hiding this comment

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

What's the premise behind splitting this line?

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, I prefer one function per line when debugging, it saves stepping in to the wrong function and my mind can parse it more easily. But no big deal either way. If this is a blocker I will reformat it.

Copy link
Member

Choose a reason for hiding this comment

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

That's reasonable, especially since usually multiple python API calls on a line means you forgot to check for NULL.

@@ -601,14 +601,14 @@ static PyObject *
fromstring_null_term_c_api(PyObject *dummy, PyObject *byte_obj)
{
char *string;
PyArray_Descr *descr;
Copy link
Member

Choose a reason for hiding this comment

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

May as well put this on line 610, unless the plan is to backport

@eric-wieser
Copy link
Member

The decref on line 3656 looks wrong to me too

@seberg
Copy link
Member
seberg commented Sep 25, 2019

Heh... now had to look again. So I think, that aside from the tests, the actual error is this DECREF (the other DECREF should stay as it is), in that it has to be removed:

if (ret == NULL) {

array_fromtext steals a reference, but has a cryptic comment When dtype->subarray is true, PyArray_NewFromDescr will decref dtype (also frombinary) which my guess says that the descriptor can be replaced by PyArray_NewFromDescr, so that they function would have to use PyArray_DESCR(...) or, as it does, temporarily hold on to the original descriptor that was passed in.

@eric-wieser
Copy link
Member

I think array_fromfile_binary also needs a comment about whether it steals a reference.

For simplicity, can we rewrite these internal helpers to not steal a reference? From what I've seen so far, places where references are stolen are inevitably the places where we clean up badly.

@seberg
Copy link
Member
seberg commented Sep 25, 2019

For simplicity, can we rewrite these internal helpers to not steal a reference? From what I've seen so far, places where references are stolen are inevitably the places where we clean up badly.

I am fine with that, it seems like it does end up being to smart. To defend the old code a bit, I think all array creation functions steal the reference to the descritpor.

@seberg
Copy link
Member
seberg commented Oct 2, 2019

OK, I ran refcount checker over test_deprecations and test_multiarray, and things seem fine.

@@ -3793,7 +3786,6 @@ PyArray_FromFile(FILE *fp, PyArray_Descr *dtype, npy_intp num, char *sep)
(skip_separator) fromfile_skip_separator, NULL);
}
if (ret == NULL) {
Py_DECREF(dtype);
Copy link
Member
@seberg seberg Oct 2, 2019

Choose a reason for hiding this comment

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

Just to be clear, this line is likely the only actual bug. Everything else is now just cleanup...

@mattip mattip changed the title BUG: dtype references are borrowed BUG: dtype refcount cleanups Oct 3, 2019
@mattip mattip merged commit 454c5b5 into numpy:master Oct 3, 2019
@pv
Copy link
Member
pv commented Oct 5, 2019

git bisect says this PR broke scipy.io.FortranFile, see scipy/scipy#10903,
454c5b5e9a76809a4ab60bda30aa048ec37ee11e is the first bad commit.
Test: python3 -mpytest --pyargs scipy.io.tests.test_fortran
Didn't look why, but it's pure-python code...

return NULL;
}
/* In some cases NewFromDescr can replace the dtype, so fetch new one */
dtype = PyArray_DESCR(r);
Copy link
Member
@pv pv Oct 5, 2019

Choose a reason for hiding this comment

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

This I think breaks reading sub-array dtypes:

# After this PR
>>> import numpy as np
>>> with open('/dev/zero', 'rb') as f:
...     a = np.fromfile(f, dtype='(8,8)u8', count=1)
... 
>>> (a == 0).all()
False

The reason I guess is that creating an array with sub-array dtype moves the array dimensions from the dtype to the array itself, so the number of bytes to read becomes incorrect.

Copy link
Member

Choose a reason for hiding this comment

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

Oh should have thought of that. Yeah, if I do it like that, I also have to update the num from the actual array...

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.

5 participants
0