Skip to main content

Find out what’s still holding a reference to an object that should be dead.

refleak.testing.assert_no_instances(cls) checks that no instances of cls remain alive after garbage collection, and for any that do, reports a rendered referrer chain – what’s still holding on to it, and (recursively) what’s holding on to that – so tracking down a reference/GC leak (e.g. a lingering Qt widget, VTK actor, or GUI object in tests) doesn’t require manually poking at gc.get_referrers by hand.

Extracted from mne.utils.misc._assert_no_instances, developed over several years of tracking down reference leaks in MNE-Python, PyVista, and pyvistaqt.

Installation can be performed via pip:

pip install refleak

Usage

import gc
from refleak import testing


class Leaky:
    pass


_leaked = Leaky()  # e.g. accidentally kept alive by a module-level cache
del _leaked
gc.collect()
testing.assert_no_instances(Leaky, when="after test")

A common pattern is a pytest fixture that runs the check on teardown:

import pytest
from refleak.testing import assert_no_instances


@pytest.fixture
def check_no_leaked_widgets(request):
    yield
    assert_no_instances(MyWidget, when="test teardown", request=request)

When references are held, an AssertionError will be thrown. For example:

import gc
from refleak import testing

class Leaky:
    pass

class ClingyParent:
    some_dict: dict

leaked = Leaky()
parent = ClingyParent()
parent.some_dict = {"leak_1": leaked}  # e.g. accidentally kept alive by some object
root_list = ["some_str", leaked, "some_other_str"]
del leaked
gc.collect()
testing.assert_no_instances(Leaky, when="after test")

Would result in:

AssertionError:
Found 1 __main__.Leaky @ after test:
Leaky @ 0x102fe3e00:
├── dict['leak_1']: dict = <len=1>
│   └── __main__.ClingyParent.__dict__['some_dict']: dict = <len=1>
│       └── __main__.__dict__['parent']: dict = <len=15>
└── __main__.root_list[1]: list = <len=3>

Snapshots: only flag new instances

assert_no_instances requires that zero instances exist, which is too strict when some legitimately pre-date the code under test – for example VTK objects held by a plotting theme or a module-level cache. Snapshot records the matching objects that already exist so that only ones created afterwards (and still alive) are reported:

import pytest
from refleak.testing import Snapshot


@pytest.fixture(autouse=True)
def check_vtk_gc(request):
    """Ensure no VTK objects created during a test outlive it."""

    def is_vtk(obj):
        return obj.__class__.__name__.startswith("vtk")

    snap = Snapshot(is_vtk, label="VTK")
    yield
    snap.assert_no_new(when="test teardown", request=request)

match can be a type, a tuple of types, or a predicate callable, and failures render the same referrer chains as assert_no_instances.

Freeze mode: faster and stricter snapshots

By default a Snapshot records the id() of every matching object, which costs a gc.collect() and a full heap scan up front, plus another scan at check time. freeze=True instead calls gc.freeze(), moving every live object into the permanent generation – which gc.get_objects() never reports and the collector never walks – so at check time everything still visible is by construction newer than the snapshot:

@pytest.fixture(autouse=True)
def check_vtk_gc(request):
    snap = Snapshot(is_vtk, label="VTK", freeze=True)
    try:
        yield
        snap.assert_no_new(when="test teardown", request=request)
    finally:
        snap.thaw()  # no-op if the check above ran (it thaws itself)

Nothing is recorded and nothing is scanned at snapshot time (~0.1 ms instead of ~60 ms on a 180k-object heap; one downstream suite went from 155 s to 83 s), and the check is also stricter: an id() is an address, and CPython readily hands a freed address straight back to the next object of the same size, so an id-based snapshot can mistake a genuine leak for the pre-existing object it replaced. Freezing has no ids to collide.

The trade-off is that freezing is process-wide until thaw() (which assert_no_new calls for you, on every path including a failing one), and for that whole window gc.get_objects() and gc.get_referrers() lie to everyone – e.g. Hypothesis’s register_random checks reachability with gc.get_referrers() and warns spuriously inside a frozen window. Keep the frozen window as small as the code under test.

Comparison to similar packages

There’s no shortage of tools for poking at Python’s garbage collector; here’s how refleak fits in relative to the ones people reach for most. “Monthly downloads” is from PyPI Stats (July 2026) and includes CI/mirror traffic, so treat it as a rough popularity signal rather than a count of individual users. “Releases (5y)” counts releases in the last five years as a rough maintenance signal.

Package

What it does

Monthly downloads

Latest release

Releases (5y)

refleak

Assert no instances of a class remain alive; on failure, render the referrer chain keeping each survivor alive

new

objgraph

General-purpose object-graph exploration: count objects by type, diff growth between snapshots, render backref/reference graphs via Graphviz

~1.1M

3.6.2 (Oct 2024)

3

Pympler

Broader memory-profiling suite: object sizing (asizeof), live monitoring (muppy), and class-level lifetime tracking (ClassTracker)

~5.5M

1.1 (Jun 2024)

3

guppy3 (heapy)

Python 3 port of the classic guppy/heapy heap analysis toolset, with a query language for slicing the whole heap by type, size, or referrer

~1.2M

3.1.7 (May 2026)

7

pytest-leaks

pytest plugin that reruns each test several times and watches sys.gettotalrefcount() for growth, rather than checking specific classes

~2.4k

0.3.1 (Nov 2019)

0 (unmaintained)

None of the alternatives above do exactly what refleak does: assert that no instances of a specific class remain alive and, on failure, explain why via a rendered referrer chain, in a form meant to be dropped straight into a test suite’s teardown. objgraph and guppy3/heapy can answer the same “why is this still alive” question (and go well beyond it, e.g. full heap graphs and queries), but require driving their APIs interactively or wiring up Graphviz output yourself rather than getting an assertion with a readable message for free. Pympler is aimed more at memory sizing and monitoring over time than one-shot leak assertions. pytest-leaks checks for leaks generically (via total refcount growth across repeated runs) instead of targeting specific classes, so it can flag that something leaked without telling you what or why. If you need full heap introspection or memory-size profiling, reach for objgraph or Pympler/guppy3 instead; if you just want a pytest-friendly assertion that a GUI widget, VTK actor, or other object didn’t leak, and a readable explanation when it did, that’s what refleak is for.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

refleak-0.2.2.tar.gz (37.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

refleak-0.2.2-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

File details

Details for the file refleak-0.2.2.tar.gz.

File metadata

  • Download URL: refleak-0.2.2.tar.gz
  • Upload date:
  • Size: 37.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for refleak-0.2.2.tar.gz
Algorithm Hash digest
SHA256 c9cfd5054e1703edf4489fb0418aee930cb6f0eeb4f64fb81a6a64772d1841c2
MD5 2a8be36c43ca9ec1cabf86fb79d0cae6
BLAKE2b-256 aab167b11c3f6ed3ed43aaf3498b22da7e65d835d795016f0072e5f497c68274

See more details on using hashes here.

Provenance

The following attestation bundles were made for refleak-0.2.2.tar.gz:

Publisher: release.yml on mne-tools/refleak

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file refleak-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: refleak-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 27.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for refleak-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 395675d59b07bf3fd8730bacb5c947c30d3474a6aff1efabdd4f7d2d8689d37b
MD5 b1a8c6cc577a549f363a46e471192b40
BLAKE2b-256 9a435d968309f1749045aa4ef9dc044f456ed1c8882e269d16e0b2df168b969c

See more details on using hashes here.

Provenance

The following attestation bundles were made for refleak-0.2.2-py3-none-any.whl:

Publisher: release.yml on mne-tools/refleak

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page