Skip to content

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.

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.

}

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 👍

bennorth added 2 commits July 20, 2017 08:38
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