[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.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):
|
||||
|
|
|
@ -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(&unreachable, PREV_MASK_COLLECTING);
|
||||
|
||||
/* Collect statistics on collectable objects found and print
|
||||
* debugging information.
|
||||
*/
|
||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
||||
m++;
|
||||
if (state->debug & DEBUG_COLLECTABLE) {
|
||||
/* Print debugging information. */
|
||||
if (state->debug & DEBUG_COLLECTABLE) {
|
||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(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
|
||||
* in finalizers to be freed.
|
||||
*/
|
||||
m += gc_list_size(&unreachable);
|
||||
delete_garbage(state, &unreachable, old);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue