# The problem may be intractable, given the current gc+weakref design. import weakref import gc gc.set_debug(gc.DEBUG_STATS) class C(object): def __init__(self, i): self.i = i self.loop = self c0 = C(0) # Move all the above into generation 2. gc.collect() c1 = C(1) c1.keep_c0_alive = c0 del c0.loop # now only c1 keeps c0 alive c2 = C(2) c2wr = weakref.ref(c2) # no callback! ouch = [] def callback(ignored): ouch[:] = [c2wr()] # The callback gets associated with a wr on an object in generation 2. c0wr = weakref.ref(c0, callback) print "id(c0)", id(c0) print "id(c1)", id(c1) print "id(c2)", id(c2) c0 = c1 = c2 = None # What we've set up: c0, c1, and c2 are all trash now. c0 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's also a global # weakref to c0 (c0wr), and that does have a callback, and that callback # references c2 via c2wr(). # # c0 has a wr with callback, which 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 c0 at all, abd c0 is the only object # that has a weakref with a callback. gc clears c1 and c2. Clearing c1 has # the side effect of dropping the refcount on c0 to 0, so c0 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.