[3.7] bpo-38379: don't claim objects are collected when they aren't … · python/cpython@a081e93 (original) (raw)

`@@ -755,6 +755,77 @@ def test_freeze(self):

`

755

755

`gc.unfreeze()

`

756

756

`self.assertEqual(gc.get_freeze_count(), 0)

`

757

757

``

``

758

`+

def test_38379(self):

`

``

759

`+

When a finalizer resurrects objects, stats were reporting them as

`

``

760

`+

having been collected. This affected both collect()'s return

`

``

761

`+

value and the dicts returned by get_stats().

`

``

762

`+

N = 100

`

``

763

+

``

764

`+

class A: # simple self-loop

`

``

765

`+

def init(self):

`

``

766

`+

self.me = self

`

``

767

+

``

768

`+

class Z(A): # resurrecting del

`

``

769

`+

def del(self):

`

``

770

`+

zs.append(self)

`

``

771

+

``

772

`+

zs = []

`

``

773

+

``

774

`+

def getstats():

`

``

775

`+

d = gc.get_stats()[-1]

`

``

776

`+

return d['collected'], d['uncollectable']

`

``

777

+

``

778

`+

gc.collect()

`

``

779

`+

gc.disable()

`

``

780

+

``

781

`+

No problems if just collecting A() instances.

`

``

782

`+

oldc, oldnc = getstats()

`

``

783

`+

for i in range(N):

`

``

784

`+

A()

`

``

785

`+

t = gc.collect()

`

``

786

`+

c, nc = getstats()

`

``

787

`+

self.assertEqual(t, 2*N) # instance object & its dict

`

``

788

`+

self.assertEqual(c - oldc, 2*N)

`

``

789

`+

self.assertEqual(nc - oldnc, 0)

`

``

790

+

``

791

`+

But Z() is not actually collected.

`

``

792

`+

oldc, oldnc = c, nc

`

``

793

`+

Z()

`

``

794

`+

Nothing is collected - Z() is merely resurrected.

`

``

795

`+

t = gc.collect()

`

``

796

`+

c, nc = getstats()

`

``

797

`+

#self.assertEqual(t, 2) # before

`

``

798

`+

self.assertEqual(t, 0) # after

`

``

799

`+

#self.assertEqual(c - oldc, 2) # before

`

``

800

`+

self.assertEqual(c - oldc, 0) # after

`

``

801

`+

self.assertEqual(nc - oldnc, 0)

`

``

802

+

``

803

`+

Unfortunately, a Z() prevents anything from being collected.

`

``

804

`+

It should be possible to collect the A instances anyway, but

`

``

805

`+

that will require non-trivial code changes.

`

``

806

`+

oldc, oldnc = c, nc

`

``

807

`+

for i in range(N):

`

``

808

`+

A()

`

``

809

`+

Z()

`

``

810

`+

Z() prevents anything from being collected.

`

``

811

`+

t = gc.collect()

`

``

812

`+

c, nc = getstats()

`

``

813

`+

#self.assertEqual(t, 2*N + 2) # before

`

``

814

`+

self.assertEqual(t, 0) # after

`

``

815

`+

#self.assertEqual(c - oldc, 2*N + 2) # before

`

``

816

`+

self.assertEqual(c - oldc, 0) # after

`

``

817

`+

self.assertEqual(nc - oldnc, 0)

`

``

818

+

``

819

`+

But the A() trash is reclaimed on the next run.

`

``

820

`+

oldc, oldnc = c, nc

`

``

821

`+

t = gc.collect()

`

``

822

`+

c, nc = getstats()

`

``

823

`+

self.assertEqual(t, 2*N)

`

``

824

`+

self.assertEqual(c - oldc, 2*N)

`

``

825

`+

self.assertEqual(nc - oldnc, 0)

`

``

826

+

``

827

`+

gc.enable()

`

``

828

+

758

829

``

759

830

`class GCCallbackTests(unittest.TestCase):

`

760

831

`def setUp(self):

`