DEV Community

TildAlice
TildAlice

Posted on • Originally published at tildalice.io

__del__ Cyclic References Leak Memory: 3 Patterns That Fail

When Python's Garbage Collector Gives Up

You add a __del__ method to clean up resources. Tests pass. Code ships. Then production memory climbs to 8GB over three days and you're debugging why objects that should be dead are still holding file handles.

The culprit? Cyclic references combined with custom destructors. Python's garbage collector can detect cycles, but it refuses to break them when __del__ is involved. The objects sit in memory forever, leaked but not forgotten.

Here's the pattern that bites everyone once:

import sys

class Connection:
    def __init__(self, name):
        self.name = name
        self.logger = Logger(self)  # Logger holds reference back to Connection

    def __del__(self):
        print(f"Closing {self.name}")

class Logger:
    def __init__(self, connection):
        self.connection = connection  # Cyclic reference created

conn = Connection("db-primary")
del conn
print(f"Objects in gc: {len([obj for obj in gc.get_objects() if isinstance(obj, Connection)])}")
Enter fullscreen mode Exit fullscreen mode

You'd expect "Closing db-primary" to print immediately. It doesn't. The Connection object leaks because Python sees the cycle ($\text{Connection} \rightarrow \text{Logger} \rightarrow \text{Connection}$) and thinks: "If I break this cycle by deleting one object first, its __del__ might access the other object that I haven't deleted yet. Undefined behavior. I'm not touching this."

And so the garbage collector just... doesn't collect it.


Continue reading the full article on TildAlice

Top comments (0)