diff options
author | Tim Peters <tim.peters@gmail.com> | 2003-04-06 00:11:39 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2003-04-06 00:11:39 (GMT) |
commit | bf384c256e0513eba7bc59cc5f8fa36b34c62c96 (patch) | |
tree | bb0f732b35f4ad7b6df08dc187eaed078da576e0 /Lib | |
parent | f6ae7a43eb781f2922309278d8005415e507ad28 (diff) | |
download | cpython-bf384c256e0513eba7bc59cc5f8fa36b34c62c96.zip cpython-bf384c256e0513eba7bc59cc5f8fa36b34c62c96.tar.gz cpython-bf384c256e0513eba7bc59cc5f8fa36b34c62c96.tar.bz2 |
Reworked move_finalizer_reachable() to create two distinct lists:
externally unreachable objects with finalizers, and externally unreachable
objects without finalizers reachable from such objects. This allows us
to call has_finalizer() at most once per object, and so limit the pain of
nasty getattr hooks. This fixes the failing "boom 2" example Jeremy
posted (a non-printing variant of which is now part of test_gc), via never
triggering the nasty part of its __getattr__ method.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_gc.py | 36 |
1 files changed, 32 insertions, 4 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 5ec87b9..e225881 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -253,21 +253,21 @@ def test_trashcan(): v = {1: v, 2: Ouch()} gc.disable() -class C: +class Boom: def __getattr__(self, someattribute): del self.attr raise AttributeError def test_boom(): - a = C() - b = C() + a = Boom() + b = Boom() a.attr = b b.attr = a gc.collect() garbagelen = len(gc.garbage) del a, b - # a<->b are in a trash cycle now. Collection will invoke C.__getattr__ + # a<->b are in a trash cycle now. Collection will invoke Boom.__getattr__ # (to see whether a and b have __del__ methods), and __getattr__ deletes # the internal "attr" attributes as a side effect. That causes the # trash cycle to get reclaimed via refcounts falling to 0, thus mutating @@ -276,6 +276,33 @@ def test_boom(): expect(gc.collect(), 0, "boom") expect(len(gc.garbage), garbagelen, "boom") +class Boom2: + def __init__(self): + self.x = 0 + + def __getattr__(self, someattribute): + self.x += 1 + if self.x > 1: + del self.attr + raise AttributeError + +def test_boom2(): + a = Boom2() + b = Boom2() + a.attr = b + b.attr = a + + gc.collect() + garbagelen = len(gc.garbage) + del a, b + # Much like test_boom(), except that __getattr__ doesn't break the + # cycle until the second time gc checks for __del__. As of 2.3b1, + # there isn't a second time, so this simply cleans up the trash cycle. + # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get reclaimed + # this way. + expect(gc.collect(), 4, "boom2") + expect(len(gc.garbage), garbagelen, "boom2") + def test_all(): gc.collect() # Delete 2nd generation garbage run_test("lists", test_list) @@ -295,6 +322,7 @@ def test_all(): run_test("saveall", test_saveall) run_test("trashcan", test_trashcan) run_test("boom", test_boom) + run_test("boom2", test_boom2) def test(): if verbose: |