mirror of
https://github.com/python/cpython.git
synced 2025-11-03 19:34:08 +00:00
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.
This commit is contained in:
parent
01171ebd96
commit
ecbf35f933
3 changed files with 75 additions and 6 deletions
|
|
@ -822,6 +822,76 @@ class GCTests(unittest.TestCase):
|
||||||
self.assertRaises(TypeError, gc.get_objects, "1")
|
self.assertRaises(TypeError, gc.get_objects, "1")
|
||||||
self.assertRaises(TypeError, gc.get_objects, 1.234)
|
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):
|
class GCCallbackTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
When cyclic garbage collection (gc) runs finalizers that resurrect unreachable objects, the current gc run ends, without collecting any cyclic trash. However, the statistics reported by ``collect()`` and ``get_stats()`` claimed that all cyclic trash found was collected, and that the resurrected objects were collected. Changed the stats to report that none were collected.
|
||||||
|
|
@ -1095,12 +1095,9 @@ collect(struct _gc_runtime_state *state, int generation,
|
||||||
validate_list(&finalizers, 0);
|
validate_list(&finalizers, 0);
|
||||||
validate_list(&unreachable, PREV_MASK_COLLECTING);
|
validate_list(&unreachable, PREV_MASK_COLLECTING);
|
||||||
|
|
||||||
/* Collect statistics on collectable objects found and print
|
/* Print debugging information. */
|
||||||
* debugging information.
|
|
||||||
*/
|
|
||||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
|
||||||
m++;
|
|
||||||
if (state->debug & DEBUG_COLLECTABLE) {
|
if (state->debug & DEBUG_COLLECTABLE) {
|
||||||
|
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
||||||
debug_cycle("collectable", FROM_GC(gc));
|
debug_cycle("collectable", FROM_GC(gc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1122,6 +1119,7 @@ collect(struct _gc_runtime_state *state, int generation,
|
||||||
* the reference cycles to be broken. It may also cause some objects
|
* the reference cycles to be broken. It may also cause some objects
|
||||||
* in finalizers to be freed.
|
* in finalizers to be freed.
|
||||||
*/
|
*/
|
||||||
|
m += gc_list_size(&unreachable);
|
||||||
delete_garbage(state, &unreachable, old);
|
delete_garbage(state, &unreachable, old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue