Skip to content

py::keep_alive: nurse destructor can run before patient's #856

Closed
@bmerry

Description

@bmerry

Issue description

The py::keep_alive weakref trick doesn't always work. In some cases (at least in Python 2.7, haven't tried 3), the weakref callback can run before the referenced object is cleared. This happens in the cyclic garbage collector: first all weakref callbacks are handled (handle_weakrefs function), then the garbage is cleared. If the nurse is being destroyed on this path, then it can lead to the patient destructor running before the nurse's (leading to undefined behaviour if the destructor of the nurse references the patient). Note that the nurse doesn't itself have to be part of a reference cycle, it just has to be reachable from one. However, it needs to have Py_TPFLAGS_HAVE_GC. In the example code I've ensured that by specifying py::dynamic_attr, but I think when I originally encountered the problem it was set due to having a declared base.

I've had similar issues with Boost.Python before. I suspect the weakref approach is fundamentally untenable; it may be necessary to store a list of handles (with strong references) in the PyObject and decref them when the object is cleared.

Reproducible example code

keepalive.cpp:

#include <pybind11/pybind11.h>

namespace py = pybind11;

class B;

class A {
private:
    B &b;
public:
    A(B &b) : b(b) {}
    ~A() { py::print("In A::~A()"); }
};

class B {
public:
    ~B() { py::print("In B::~B()"); }
};

PYBIND11_PLUGIN(keepalive) {
    py::module m("keepalive");

    py::class_<A>(m, "A", py::dynamic_attr())
        .def(py::init<B &>(), py::keep_alive<1, 2>());

    py::class_<B>(m, "B").def(py::init<>());
    return m.ptr();
}

test.py:

from keepalive import A, B

lst = [A(B())]
lst.append(lst)
del lst

Output is

In B::~B()
In A::~A()

Expected output is the other way around.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions