11from test .test_support import verify , verbose , TestFailed , vereq
22import sys
33import gc
4+ import weakref
45
56def expect (actual , expected , name ):
67 if actual != expected :
@@ -369,6 +370,191 @@ def test_get_referents():
369370
370371 expect (gc .get_referents (1 , 'a' , 4j ), [], "get_referents" )
371372
373+ # Bug 1055820 has several tests of longstanding bugs involving weakrefs and
374+ # cyclic gc.
375+
376+ # An instance of C1055820 has a self-loop, so becomes cyclic trash when
377+ # unreachable.
378+ class C1055820 (object ):
379+ def __init__ (self , i ):
380+ self .i = i
381+ self .loop = self
382+
383+ class GC_Detector (object ):
384+ # Create an instance I. Then gc hasn't happened again so long as
385+ # I.gc_happened is false.
386+
387+ def __init__ (self ):
388+ self .gc_happened = False
389+
390+ def it_happened (ignored ):
391+ self .gc_happened = True
392+
393+ # Create a piece of cyclic trash that triggers it_happened when
394+ # gc collects it.
395+ self .wr = weakref .ref (C1055820 (666 ), it_happened )
396+
397+ def test_bug1055820b ():
398+ # Corresponds to temp2b.py in the bug report.
399+
400+ ouch = []
401+ def callback (ignored ):
402+ ouch [:] = [wr () for wr in WRs ]
403+
404+ Cs = [C1055820 (i ) for i in range (2 )]
405+ WRs = [weakref .ref (c , callback ) for c in Cs ]
406+ c = None
407+
408+ gc .collect ()
409+ expect (len (ouch ), 0 , "bug1055820b" )
410+ # Make the two instances trash, and collect again. The bug was that
411+ # the callback materialized a strong reference to an instance, but gc
412+ # cleared the instance's dict anyway.
413+ Cs = None
414+ gc .collect ()
415+ expect (len (ouch ), 2 , "bug1055820b" ) # else the callbacks didn't run
416+ for x in ouch :
417+ # If the callback resurrected one of these guys, the instance
418+ # would be damaged, with an empty __dict__.
419+ expect (x , None , "bug1055820b" )
420+
421+ def test_bug1055820c ():
422+ # Corresponds to temp2c.py in the bug report. This is pretty elaborate.
423+
424+ c0 = C1055820 (0 )
425+ # Move c0 into generation 2.
426+ gc .collect ()
427+
428+ c1 = C1055820 (1 )
429+ c1 .keep_c0_alive = c0
430+ del c0 .loop # now only c1 keeps c0 alive
431+
432+ c2 = C1055820 (2 )
433+ c2wr = weakref .ref (c2 ) # no callback!
434+
435+ ouch = []
436+ def callback (ignored ):
437+ ouch [:] = [c2wr ()]
438+
439+ # The callback gets associated with a wr on an object in generation 2.
440+ c0wr = weakref .ref (c0 , callback )
441+
442+ c0 = c1 = c2 = None
443+
444+ # What we've set up: c0, c1, and c2 are all trash now. c0 is in
445+ # generation 2. The only thing keeping it alive is that c1 points to it.
446+ # c1 and c2 are in generation 0, and are in self-loops. There's a global
447+ # weakref to c2 (c2wr), but that weakref has no callback. There's also
448+ # a global weakref to c0 (c0wr), and that does have a callback, and that
449+ # callback references c2 via c2wr().
450+ #
451+ # c0 has a wr with callback, which references c2wr
452+ # ^
453+ # |
454+ # | Generation 2 above dots
455+ #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .
456+ # | Generation 0 below dots
457+ # |
458+ # |
459+ # ^->c1 ^->c2 has a wr but no callback
460+ # | | | |
461+ # <--v <--v
462+ #
463+ # So this is the nightmare: when generation 0 gets collected, we see that
464+ # c2 has a callback-free weakref, and c1 doesn't even have a weakref.
465+ # Collecting generation 0 doesn't see c0 at all, and c0 is the only object
466+ # that has a weakref with a callback. gc clears c1 and c2. Clearing c1
467+ # has the side effect of dropping the refcount on c0 to 0, so c0 goes
468+ # away (despite that it's in an older generation) and c0's wr callback
469+ # triggers. That in turn materializes a reference to c2 via c2wr(), but
470+ # c2 gets cleared anyway by gc.
471+
472+ # We want to let gc happen "naturally", to preserve the distinction
473+ # between generations.
474+ junk = []
475+ i = 0
476+ detector = GC_Detector ()
477+ while not detector .gc_happened :
478+ i += 1
479+ if i > 10000 :
480+ raise TestFailed ("gc didn't happen after 10000 iterations" )
481+ expect (len (ouch ), 0 , "bug1055820c" )
482+ junk .append ([]) # this will eventually trigger gc
483+
484+ expect (len (ouch ), 1 , "bug1055820c" ) # else the callback wasn't invoked
485+ for x in ouch :
486+ # If the callback resurrected c2, the instance would be damaged,
487+ # with an empty __dict__.
488+ expect (x , None , "bug1055820c" )
489+
490+ def test_bug1055820d ():
491+ # Corresponds to temp2d.py in the bug report. This is very much like
492+ # test_bug1055820c, but uses a __del__ method instead of a weakref
493+ # callback to sneak in a resurrection of cyclic trash.
494+
495+ ouch = []
496+ class D (C1055820 ):
497+ def __del__ (self ):
498+ ouch [:] = [c2wr ()]
499+
500+ d0 = D (0 )
501+ # Move all the above into generation 2.
502+ gc .collect ()
503+
504+ c1 = C1055820 (1 )
505+ c1 .keep_d0_alive = d0
506+ del d0 .loop # now only c1 keeps d0 alive
507+
508+ c2 = C1055820 (2 )
509+ c2wr = weakref .ref (c2 ) # no callback!
510+
511+ d0 = c1 = c2 = None
512+
513+ # What we've set up: d0, c1, and c2 are all trash now. d0 is in
514+ # generation 2. The only thing keeping it alive is that c1 points to it.
515+ # c1 and c2 are in generation 0, and are in self-loops. There's a global
516+ # weakref to c2 (c2wr), but that weakref has no callback. There are no
517+ # other weakrefs.
518+ #
519+ # d0 has a __del__ method that references c2wr
520+ # ^
521+ # |
522+ # | Generation 2 above dots
523+ #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .
524+ # | Generation 0 below dots
525+ # |
526+ # |
527+ # ^->c1 ^->c2 has a wr but no callback
528+ # | | | |
529+ # <--v <--v
530+ #
531+ # So this is the nightmare: when generation 0 gets collected, we see that
532+ # c2 has a callback-free weakref, and c1 doesn't even have a weakref.
533+ # Collecting generation 0 doesn't see d0 at all. gc clears c1 and c2.
534+ # Clearing c1 has the side effect of dropping the refcount on d0 to 0, so
535+ # d0 goes away (despite that it's in an older generation) and d0's __del__
536+ # triggers. That in turn materializes a reference to c2 via c2wr(), but
537+ # c2 gets cleared anyway by gc.
538+
539+ # We want to let gc happen "naturally", to preserve the distinction
540+ # between generations.
541+ detector = GC_Detector ()
542+ junk = []
543+ i = 0
544+ while not detector .gc_happened :
545+ i += 1
546+ if i > 10000 :
547+ raise TestFailed ("gc didn't happen after 10000 iterations" )
548+ expect (len (ouch ), 0 , "bug1055820d" )
549+ junk .append ([]) # this will eventually trigger gc
550+
551+ expect (len (ouch ), 1 , "bug1055820d" ) # else __del__ wasn't invoked
552+ for x in ouch :
553+ # If __del__ resurrected c2, the instance would be damaged, with an
554+ # empty __dict__.
555+ expect (x , None , "bug1055820d" )
556+
557+
372558def test_all ():
373559 gc .collect () # Delete 2nd generation garbage
374560 run_test ("lists" , test_list )
@@ -392,6 +578,19 @@ def test_all():
392578 run_test ("boom_new" , test_boom_new )
393579 run_test ("boom2_new" , test_boom2_new )
394580 run_test ("get_referents" , test_get_referents )
581+ run_test ("bug1055820b" , test_bug1055820b )
582+
583+ gc .enable ()
584+ try :
585+ run_test ("bug1055820c" , test_bug1055820c )
586+ finally :
587+ gc .disable ()
588+
589+ gc .enable ()
590+ try :
591+ run_test ("bug1055820d" , test_bug1055820d )
592+ finally :
593+ gc .disable ()
395594
396595def test ():
397596 if verbose :
0 commit comments