wjakob / nanobind

nanobind: tiny and efficient C++/Python bindings
BSD 3-Clause "New" or "Revised" License
2.29k stars 191 forks source link

[BUG]: object won't be released until exit python #505

Closed Yikai-Liao closed 5 months ago

Yikai-Liao commented 5 months ago

Problem description

image

@wjakob Hi, sorry to bother you, but I found a possible memory leak.

If I create a c++ struct usingnanobind and don't assign it to a variable in python, the object won't be freed until I exit ipython, even if I manually call gc.collect()

Reproducible example code

#include <iostream>
#include <nanobind/nanobind.h>

struct Foo {
    int data;
    explicit Foo(int data) : data(data) {}
    ~Foo() {
        std::cout << "Foo " << data << " is destructed" << std::endl;
    }
};

namespace nb = nanobind;

NB_MODULE(core, m) {
    nb::class_<Foo>(m, "Foo")
        .def(nb::init<int>());
}
hpkfft commented 5 months ago

I believe that Foo(2) is kept alive because it is referenced by _.

>>> Foo(2)
<core.Foo object at 0x7fa465ac9ed0>
>>> _
<core.Foo object at 0x7fa465ac9ed0>
>>> 7
Foo 2 is destructed
7
>>>
Yikai-Liao commented 5 months ago

So this is a Python syntax feature?

Yikai-Liao commented 5 months ago

@hpkfft similar issue is also found in vector (using nb::bind_vector). But in this case, even if I del the vector object manually, it won't be freed until exit.

I'm not sure exactly what causes this, but this should be a different cause than the previous problem

#include <iostream>
#include <nanobind/nanobind.h>
#include <nanobind/stl/bind_vector.h>
#include <nanobind/stl/string.h>

struct Foo {
    int data;
    explicit Foo(int data) : data(data) {}
    ~Foo() {
        std::cout << "Foo " << data << " is destructed" << std::endl;
    }
};

namespace nb = nanobind;

NB_MAKE_OPAQUE(std::vector<Foo>);

NB_MODULE(core, m) {
    nb::class_<Foo>(m, "Foo")
        .def(nb::init<int>())
        .def("__repr__", [](const Foo &self) { return "Foo(" + std::to_string(self.data) + ")"; });

    m.def("foos", []() {
        return std::vector<Foo>{Foo(1), Foo(2), Foo(3)};
    });
}

image

Edit

Sorry, it was the same problem. _ will keep f alive here.

bjodah commented 5 months ago

@Yikai-Liao just FYI, you can configure your .ipython/profile_default to contain the line:

c.InteractiveShell.cache_size = 0

That disables IPython from hanging on to the output.

wjakob commented 5 months ago

This isn't a nanobind bug, I will close this ticket.