# We don't need any weakref callbacks to get hosed. import weakref import gc gc.set_debug(gc.DEBUG_STATS) class C(object): def __init__(self, i): self.i = i self.loop = self ouch = [] class D(C): def __del__(self): ouch[:] = [c2wr()] d0 = D(0) # Move all the above into generation 2. gc.collect() c1 = C(1) c1.keep_d0_alive = d0 del d0.loop # now only c1 keeps d0 alive c2 = C(2) c2wr = weakref.ref(c2) # no callback! print "id(d0)", id(d0) print "id(c1)", id(c1) print "id(c2)", id(c2) d0 = c1 = c2 = None # What we've set up: d0, c1, and c2 are all trash now. d0 is in generation # 2. The only thing keeping it alive is that c1 points to it. c1 and c2 # are in generation 0, and are in self-loops. There's a global weakref # to c2 (c2wr), but that weakref has no callback. There are no other # weakrefs. # # c0 has a __del__ method that references c2wr # ^ # | # | Generation 2 above dots #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . # | Generation 0 below dots # | # | # ^->c1 ^->c2 has a wr but no callback # | | | | # <--v <--v # # So this is the nightmare: when generation 0 gets collected, we see that # c2 has a callback-free weakref, and c1 doesn't even have a weakref. # Collecting generation 0 doesn't see d0 at all. gc clears c1 and c2 # Clearing c1 has the side effect of dropping the refcount on d0 to 0, so d0 # goes away (despite that it's in an older generation) and c2's wr callback # triggers. That in turn materializes a reference to c2 via c2wr(), but c2 # gets cleared anyway by gc. i = 0 junk = [] stillhappy = True while stillhappy: del ouch[:] junk.append([]) # this will eventually trigger gc i += 1 for x in ouch: if x is not None: assert isinstance(x, C) if x.__dict__ == {}: stillhappy = False print print "*" * 20, "C instance with id", id(x), "hosed on iteration", i print # Typical output: # # gc: collecting generation 2... # gc: objects in each generation: 324 1825 0 # gc: done. # id(c0) 10327312 # id(c1) 10327344 # id(c2) 10327376 # gc: collecting generation 0... # gc: objects in each generation: 714 0 2149 # gc: done, 4 unreachable, 0 uncollectable. # # ******************** C instance with id 10327376 hosed on iteration 705 # # gc: collecting generation 2... # gc: objects in each generation: 0 712 2141 # gc: done.