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

Add runtime setting to suppress leak warnings #109

Merged
merged 7 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ improvements for developers:

- When the python interpreter shuts down, _nanobind_ reports instance, type,
and function leaks related to bindings, which is useful for tracking down
reference counting issues.
reference counting issues.
If these warnings are undesired, call `nb::set_leak_warnings(false)` from
any one of the loaded extension modules.

- _nanobind_ deletes its internal data structures when the Python interpreter
terminates, which avoids memory leak reports in tools like _valgrind_.
Expand Down
4 changes: 4 additions & 0 deletions include/nanobind/nb_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,9 @@ NB_CORE void incref_checked(PyObject *o) noexcept;
/// Decrease the reference count of 'o', and check that the GIL is held
NB_CORE void decref_checked(PyObject *o) noexcept;

// ========================================================================

NB_CORE void set_leak_warnings(bool print_leak_warnings) noexcept;

NAMESPACE_END(detail)
NAMESPACE_END(NB_NAMESPACE)
2 changes: 2 additions & 0 deletions include/nanobind/nb_misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ template <typename T> struct deleter {
PyObject *o{nullptr};
};

void set_leak_warnings(bool print_leak_warnings) noexcept;

NAMESPACE_END(NB_NAMESPACE)
13 changes: 12 additions & 1 deletion src/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,6 @@ bool load_i64(PyObject *o, uint8_t flags, int64_t *out) noexcept {
return load_int(o, flags, out);
}


// ========================================================================

void incref_checked(PyObject *o) noexcept {
Expand All @@ -886,5 +885,17 @@ void decref_checked(PyObject *o) noexcept {
Py_DECREF(o);
}

// ========================================================================

void set_leak_warnings(bool print_leak_warnings) noexcept {
nb_internals &internals = internals_get();
internals.print_leak_warnings = print_leak_warnings;
}

NAMESPACE_END(detail)

void set_leak_warnings(bool print_leak_warnings) noexcept {
detail::set_leak_warnings(print_leak_warnings);
}

NAMESPACE_END(NB_NAMESPACE)
56 changes: 33 additions & 23 deletions src/nb_internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,41 +325,49 @@ static void internals_cleanup() {
bool leak = false;

if (!internals_p->inst_c2p.empty()) {
fprintf(stderr, "nanobind: leaked %zu instances!\n",
internals_p->inst_c2p.size());
if (internals_p->print_leak_warnings) {
fprintf(stderr, "nanobind: leaked %zu instances!\n",
internals_p->inst_c2p.size());
}
leak = true;
}

if (!internals_p->keep_alive.empty()) {
fprintf(stderr, "nanobind: leaked %zu keep_alive records!\n",
internals_p->keep_alive.size());
if (internals_p->print_leak_warnings) {
fprintf(stderr, "nanobind: leaked %zu keep_alive records!\n",
internals_p->keep_alive.size());
}
leak = true;
}

if (!internals_p->type_c2p.empty()) {
fprintf(stderr, "nanobind: leaked %zu types!\n",
internals_p->type_c2p.size());
int ctr = 0;
for (const auto &kv : internals_p->type_c2p) {
fprintf(stderr, " - leaked type \"%s\"\n", kv.second->name);
if (ctr++ == 10) {
fprintf(stderr, " - ... skipped remainder\n");
break;
if (internals_p->print_leak_warnings) {
fprintf(stderr, "nanobind: leaked %zu types!\n",
internals_p->type_c2p.size());
int ctr = 0;
for (const auto &kv : internals_p->type_c2p) {
fprintf(stderr, " - leaked type \"%s\"\n", kv.second->name);
if (ctr++ == 10) {
fprintf(stderr, " - ... skipped remainder\n");
break;
}
}
}
leak = true;
}

if (!internals_p->funcs.empty()) {
fprintf(stderr, "nanobind: leaked %zu functions!\n",
internals_p->funcs.size());
int ctr = 0;
for (void *f : internals_p->funcs) {
fprintf(stderr, " - leaked function \"%s\"\n",
nb_func_data(f)->name);
if (ctr++ == 10) {
fprintf(stderr, " - ... skipped remainder\n");
break;
if (internals_p->print_leak_warnings) {
fprintf(stderr, "nanobind: leaked %zu functions!\n",
internals_p->funcs.size());
int ctr = 0;
for (void *f : internals_p->funcs) {
fprintf(stderr, " - leaked function \"%s\"\n",
nb_func_data(f)->name);
if (ctr++ == 10) {
fprintf(stderr, " - ... skipped remainder\n");
break;
}
}
}
leak = true;
Expand All @@ -369,8 +377,10 @@ static void internals_cleanup() {
delete internals_p;
internals_p = nullptr;
} else {
fprintf(stderr, "nanobind: this is likely caused by a reference "
"counting issue in the binding code.\n");
if (internals_p->print_leak_warnings) {
fprintf(stderr, "nanobind: this is likely caused by a reference "
"counting issue in the binding code.\n");
}

#if NB_ABORT_ON_LEAK == 1
abort(); // Extra-strict behavior for the CI server
Expand Down
3 changes: 3 additions & 0 deletions src/nb_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ struct nb_internals {

/// Registered C++ -> Python exception translators
std::vector<std::pair<exception_translator, void *>> exception_translators;

/// Boolean specifying whether to print leak warnings on exit
bool print_leak_warnings{true};
};

struct current_method {
Expand Down