summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2019-10-09 17:37:30 (GMT)
committerGitHub <noreply@github.com>2019-10-09 17:37:30 (GMT)
commitecbf35f9335b0420cb8adfda6f299d6747a16515 (patch)
tree3f011932f3e968a91391a6931346825596525f60 /Lib
parent01171ebd966b0cd6352057799ad876dd1e07942e (diff)
downloadcpython-ecbf35f9335b0420cb8adfda6f299d6747a16515.zip
cpython-ecbf35f9335b0420cb8adfda6f299d6747a16515.tar.gz
cpython-ecbf35f9335b0420cb8adfda6f299d6747a16515.tar.bz2
bpo-38379: don't claim objects are collected when they aren't (#16658)
* bpo-38379: when a finalizer resurrects an object, nothing is actually collected in this run of gc. Change the stats to relect that truth.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_gc.py70
1 files changed, 70 insertions, 0 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index 8215390..f52db1e 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -822,6 +822,76 @@ class GCTests(unittest.TestCase):
self.assertRaises(TypeError, gc.get_objects, "1")
self.assertRaises(TypeError, gc.get_objects, 1.234)
+ def test_38379(self):
+ # When a finalizer resurrects objects, stats were reporting them as
+ # having been collected. This affected both collect()'s return
+ # value and the dicts returned by get_stats().
+ N = 100
+
+ class A: # simple self-loop
+ def __init__(self):
+ self.me = self
+
+ class Z(A): # resurrecting __del__
+ def __del__(self):
+ zs.append(self)
+
+ zs = []
+
+ def getstats():
+ d = gc.get_stats()[-1]
+ return d['collected'], d['uncollectable']
+
+ gc.collect()
+ gc.disable()
+
+ # No problems if just collecting A() instances.
+ oldc, oldnc = getstats()
+ for i in range(N):
+ A()
+ t = gc.collect()
+ c, nc = getstats()
+ self.assertEqual(t, 2*N) # instance object & its dict
+ self.assertEqual(c - oldc, 2*N)
+ self.assertEqual(nc - oldnc, 0)
+
+ # But Z() is not actually collected.
+ oldc, oldnc = c, nc
+ Z()
+ # Nothing is collected - Z() is merely resurrected.
+ t = gc.collect()
+ c, nc = getstats()
+ #self.assertEqual(t, 2) # before
+ self.assertEqual(t, 0) # after
+ #self.assertEqual(c - oldc, 2) # before
+ self.assertEqual(c - oldc, 0) # after
+ self.assertEqual(nc - oldnc, 0)
+
+ # Unfortunately, a Z() prevents _anything_ from being collected.
+ # It should be possible to collect the A instances anyway, but
+ # that will require non-trivial code changes.
+ oldc, oldnc = c, nc
+ for i in range(N):
+ A()
+ Z()
+ # Z() prevents anything from being collected.
+ t = gc.collect()
+ c, nc = getstats()
+ #self.assertEqual(t, 2*N + 2) # before
+ self.assertEqual(t, 0) # after
+ #self.assertEqual(c - oldc, 2*N + 2) # before
+ self.assertEqual(c - oldc, 0) # after
+ self.assertEqual(nc - oldnc, 0)
+
+ # But the A() trash is reclaimed on the next run.
+ oldc, oldnc = c, nc
+ t = gc.collect()
+ c, nc = getstats()
+ self.assertEqual(t, 2*N)
+ self.assertEqual(c - oldc, 2*N)
+ self.assertEqual(nc - oldnc, 0)
+
+ gc.enable()
class GCCallbackTests(unittest.TestCase):
def setUp(self):