bpo-38379: don't claim objects are collected when they aren't (#16658) · python/cpython@ecbf35f (original) (raw)

`@@ -822,6 +822,76 @@ def test_get_objects_arguments(self):

`

822

822

`self.assertRaises(TypeError, gc.get_objects, "1")

`

823

823

`self.assertRaises(TypeError, gc.get_objects, 1.234)

`

824

824

``

``

825

`+

def test_38379(self):

`

``

826

`+

When a finalizer resurrects objects, stats were reporting them as

`

``

827

`+

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

`

``

828

`+

value and the dicts returned by get_stats().

`

``

829

`+

N = 100

`

``

830

+

``

831

`+

class A: # simple self-loop

`

``

832

`+

def init(self):

`

``

833

`+

self.me = self

`

``

834

+

``

835

`+

class Z(A): # resurrecting del

`

``

836

`+

def del(self):

`

``

837

`+

zs.append(self)

`

``

838

+

``

839

`+

zs = []

`

``

840

+

``

841

`+

def getstats():

`

``

842

`+

d = gc.get_stats()[-1]

`

``

843

`+

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

`

``

844

+

``

845

`+

gc.collect()

`

``

846

`+

gc.disable()

`

``

847

+

``

848

`+

No problems if just collecting A() instances.

`

``

849

`+

oldc, oldnc = getstats()

`

``

850

`+

for i in range(N):

`

``

851

`+

A()

`

``

852

`+

t = gc.collect()

`

``

853

`+

c, nc = getstats()

`

``

854

`+

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

`

``

855

`+

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

`

``

856

`+

self.assertEqual(nc - oldnc, 0)

`

``

857

+

``

858

`+

But Z() is not actually collected.

`

``

859

`+

oldc, oldnc = c, nc

`

``

860

`+

Z()

`

``

861

`+

Nothing is collected - Z() is merely resurrected.

`

``

862

`+

t = gc.collect()

`

``

863

`+

c, nc = getstats()

`

``

864

`+

#self.assertEqual(t, 2) # before

`

``

865

`+

self.assertEqual(t, 0) # after

`

``

866

`+

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

`

``

867

`+

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

`

``

868

`+

self.assertEqual(nc - oldnc, 0)

`

``

869

+

``

870

`+

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

`

``

871

`+

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

`

``

872

`+

that will require non-trivial code changes.

`

``

873

`+

oldc, oldnc = c, nc

`

``

874

`+

for i in range(N):

`

``

875

`+

A()

`

``

876

`+

Z()

`

``

877

`+

Z() prevents anything from being collected.

`

``

878

`+

t = gc.collect()

`

``

879

`+

c, nc = getstats()

`

``

880

`+

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

`

``

881

`+

self.assertEqual(t, 0) # after

`

``

882

`+

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

`

``

883

`+

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

`

``

884

`+

self.assertEqual(nc - oldnc, 0)

`

``

885

+

``

886

`+

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

`

``

887

`+

oldc, oldnc = c, nc

`

``

888

`+

t = gc.collect()

`

``

889

`+

c, nc = getstats()

`

``

890

`+

self.assertEqual(t, 2*N)

`

``

891

`+

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

`

``

892

`+

self.assertEqual(nc - oldnc, 0)

`

``

893

+

``

894

`+

gc.enable()

`

825

895

``

826

896

`class GCCallbackTests(unittest.TestCase):

`

827

897

`def setUp(self):

`