mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
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.
This commit is contained in:
parent
f6ae7a43eb
commit
bf384c256e
2 changed files with 89 additions and 35 deletions
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue