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

Fix refcounting for tp_base objects of new types #950

Merged
merged 6 commits into from
Jul 20, 2017

Conversation

bennorth
Copy link
Contributor

Hi,

I recently hit segfaults on Python exit while using a pybind11-based module. I have found and fixed a bug in it, but while investigating, found what I think are bugs in pybind11 too. I think the code in make_static_property_type() needs to inc_ref() PyProperty_Type because of its use as the tp_base of the new type; similarly for other assignments to a new type's tp_base. In my case, enough refs were being destroyed that on exit, Python tried to dealloc PyProperty_Type, with bad results.

In pure Python, creating a heap-subtype of 'property' increases the refcount of property (i.e., PyProperty_Type) by 3:

import sys
print('before:', sys.getrefcount(property))
st = type('st', (property,), {})
print('after: ', sys.getrefcount(property))
before: 23
after:  26

This makes sense; the three new references in st are: the __base__; the sole entry in the __bases__ tuple; the entry in the __mro__.

However, importing a trivial pybind11-based module only increases the refcount by 2 even though importing the module also creates a heap-subtype of property (in make_static_property_type()):

#include <pybind11/pybind11.h>
struct Empty {};
PYBIND11_MODULE(empty_struct, m)
{ pybind11::class_<Empty>(m, "Empty"); }
# ... compile into 'empty_struct' module ...
import sys
print('before:', sys.getrefcount(property))
import empty_struct
print('after: ', sys.getrefcount(property))
before: 23
after:  25

While reading around, I found Python issue 980082 which suggests a new type's tp_base ought to be inc_ref()'d. Also, pybind11's make_new_python_type() does inc_ref() the tp_base of the new type.

This PR inc_ref()s the other three assignments to a new type's tp_base. This seemed just about worth making a tiny function for.

I've targeted master with this PR but should it also be considered for version branches?

Thanks!

To fix a difficult-to-reproduce segfault on Python interpreter exit,
ensure that the tp_base field of a handful of new heap-types is
counted as a reference to that base type object.
Copy link
Member

@jagerman jagerman left a comment

Choose a reason for hiding this comment

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

Looks like a good catch and fix, with a couple very minor changes.

@@ -27,6 +27,10 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec
return PyProperty_Type.tp_descr_set(self, cls, value);
}

static PyTypeObject *type_with_ref_incd(PyTypeObject *type) {
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't need to be static, but rather inline.

Copy link
Member

Choose a reason for hiding this comment

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

Also, this needs to be moved outside the #if !defined(PYPY_VERSION) as it gets used later on under PyPy as well.

@@ -27,6 +27,10 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec
return PyProperty_Type.tp_descr_set(self, cls, value);
}

static PyTypeObject *type_with_ref_incd(PyTypeObject *type) {
return (PyTypeObject *)(handle((PyObject *)(type)).inc_ref().ptr());
Copy link
Member

Choose a reason for hiding this comment

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

This should be equivalent, but it's a bit more self-documenting:

    return (PyTypeObject *) reinterpret_borrow<object>((PyObject *) type).release().ptr();

Copy link
Member

Choose a reason for hiding this comment

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

Actually, scracth that: it's much simpler to just bypass the pybind helper classes completely here:

    Py_INCREF(type);
    return type;

@bennorth
Copy link
Contributor Author

Thanks for the feedback. I've implemented your suggestions as individual commits for clarity, but I can squash down to a single commit later if preferred.

@jagerman
Copy link
Member

No need to squash the PR (for a single-commit PR github has a "squash and merge" button).

@wjakob
Copy link
Member

wjakob commented Jul 19, 2017

For consistency, how about also using type_with_ref_incd() in class_support.h:555? I would also suggest renaming it to type_incref()

@wjakob
Copy link
Member

wjakob commented Jul 19, 2017

Great refcounting detective work by the way 👍

Replace existing reference-handling with call to type_incref() when
setting tp_base.
@bennorth
Copy link
Contributor Author

@wjakob: Thanks for the feedback (and kind words). I've pushed new commits implementing your suggestions.

@wjakob
Copy link
Member

wjakob commented Jul 20, 2017

This looks good to merge to me (assuming that the CI bots finish successfully).

@jagerman jagerman merged commit cb3d406 into pybind:master Jul 20, 2017
@jagerman
Copy link
Member

Merged. Thanks for tracking this down and the PR!

@dean0x7d dean0x7d modified the milestone: v2.2 Aug 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants