[3.8] bpo-38379: don't claim objects are collected when they aren't (GH-16658) (GH-16683)
* [bpo-38379](https://bugs.python.org/issue38379): when a finalizer resurrects an object,
nothing is actually collected in this run of gc.
Change the stats to relect that truth.
(cherry picked from commit ecbf35f933
)
Co-authored-by: Tim Peters <tim.peters@gmail.com>
https://bugs.python.org/issue38379
Automerge-Triggered-By: @pablogsal
This commit is contained in:
parent
359a1975cb
commit
0bd9fac7a8
|
@ -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.
|
if (state->debug & DEBUG_COLLECTABLE) {
|
||||||
*/
|
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
||||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
|
||||||
m++;
|
|
||||||
if (state->debug & DEBUG_COLLECTABLE) {
|
|
||||||
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…
Reference in New Issue