gc.garbage is your friend -- Object cycles in Python
You probably never think of this when writing code, but Python relies on a garbage collector to clean up (most) objects. Rightly so. Most of the time, ignoring the GC is the right thing to do - “it just works”.
The “it just works” approach also extends to object cycles - where you
put obj1 into obj2 and obj2 into obj1. Now I’ve already linked to the gc
docs and the title gives it away as well: this breaks down if you have a
del
method in one of the objects!
This is spelled out quite clearly in the __del__
docs as well as in the
gc
doc.
So, when you have such a situation, gc.garbage
will become your new
friend. To use it, you don’t need to do anything! Just look at it!
By default, gc.garbage will be the list of all objects the gc couldn’t
clean up that also happen to have a __del__
method. So this tells you
the object that prevented the standard gc behaviour (which is: just zap
everything).
Unfortunately, this is often not enough to debug the issue at hand. To
also see all the objects that weren’t collected without a __del__
method - which usually are these that somehow hold your
object-with-__del__
, do this in your wrapper code:
gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_COLLECTABLE)
[your code]
gc.collect()
logger.debug('gc garbage: %r', gc.garbage)
for o in gc.garbage:
for r in gc.get_referrers(o):
logger.debug('ref for %r: %r', o, r)
(I assume that you already have a wrapper. If you need this, you most likely already use a wrapper for profiling or other one-time initialization or shutdown code.)
Note that gc.garbage
istself also holds references to the objects in
question, and therefore keeps them alive. Therefore, you’ll see
gc.garbage
itself as a reference to your objects in the debug
output. Ignore it. (It looks like a standard list.)
Now that you see every object that wasn’t collected, you’ll also see the
object that holds your special object with the __del__
method.
Good luck unwinding your object cycle!