17db96d56Sopenharmony_ciimport unittest 27db96d56Sopenharmony_ciimport unittest.mock 37db96d56Sopenharmony_cifrom test.support import (verbose, refcount_test, 47db96d56Sopenharmony_ci cpython_only, requires_subprocess) 57db96d56Sopenharmony_cifrom test.support.import_helper import import_module 67db96d56Sopenharmony_cifrom test.support.os_helper import temp_dir, TESTFN, unlink 77db96d56Sopenharmony_cifrom test.support.script_helper import assert_python_ok, make_script 87db96d56Sopenharmony_cifrom test.support import threading_helper 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ciimport gc 117db96d56Sopenharmony_ciimport sys 127db96d56Sopenharmony_ciimport sysconfig 137db96d56Sopenharmony_ciimport textwrap 147db96d56Sopenharmony_ciimport threading 157db96d56Sopenharmony_ciimport time 167db96d56Sopenharmony_ciimport weakref 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_citry: 197db96d56Sopenharmony_ci from _testcapi import with_tp_del 207db96d56Sopenharmony_ciexcept ImportError: 217db96d56Sopenharmony_ci def with_tp_del(cls): 227db96d56Sopenharmony_ci class C(object): 237db96d56Sopenharmony_ci def __new__(cls, *args, **kwargs): 247db96d56Sopenharmony_ci raise TypeError('requires _testcapi.with_tp_del') 257db96d56Sopenharmony_ci return C 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_citry: 287db96d56Sopenharmony_ci from _testcapi import ContainerNoGC 297db96d56Sopenharmony_ciexcept ImportError: 307db96d56Sopenharmony_ci ContainerNoGC = None 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_ci### Support code 337db96d56Sopenharmony_ci############################################################################### 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ci# Bug 1055820 has several tests of longstanding bugs involving weakrefs and 367db96d56Sopenharmony_ci# cyclic gc. 377db96d56Sopenharmony_ci 387db96d56Sopenharmony_ci# An instance of C1055820 has a self-loop, so becomes cyclic trash when 397db96d56Sopenharmony_ci# unreachable. 407db96d56Sopenharmony_ciclass C1055820(object): 417db96d56Sopenharmony_ci def __init__(self, i): 427db96d56Sopenharmony_ci self.i = i 437db96d56Sopenharmony_ci self.loop = self 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ciclass GC_Detector(object): 467db96d56Sopenharmony_ci # Create an instance I. Then gc hasn't happened again so long as 477db96d56Sopenharmony_ci # I.gc_happened is false. 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci def __init__(self): 507db96d56Sopenharmony_ci self.gc_happened = False 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci def it_happened(ignored): 537db96d56Sopenharmony_ci self.gc_happened = True 547db96d56Sopenharmony_ci 557db96d56Sopenharmony_ci # Create a piece of cyclic trash that triggers it_happened when 567db96d56Sopenharmony_ci # gc collects it. 577db96d56Sopenharmony_ci self.wr = weakref.ref(C1055820(666), it_happened) 587db96d56Sopenharmony_ci 597db96d56Sopenharmony_ci@with_tp_del 607db96d56Sopenharmony_ciclass Uncollectable(object): 617db96d56Sopenharmony_ci """Create a reference cycle with multiple __del__ methods. 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ci An object in a reference cycle will never have zero references, 647db96d56Sopenharmony_ci and so must be garbage collected. If one or more objects in the 657db96d56Sopenharmony_ci cycle have __del__ methods, the gc refuses to guess an order, 667db96d56Sopenharmony_ci and leaves the cycle uncollected.""" 677db96d56Sopenharmony_ci def __init__(self, partner=None): 687db96d56Sopenharmony_ci if partner is None: 697db96d56Sopenharmony_ci self.partner = Uncollectable(partner=self) 707db96d56Sopenharmony_ci else: 717db96d56Sopenharmony_ci self.partner = partner 727db96d56Sopenharmony_ci def __tp_del__(self): 737db96d56Sopenharmony_ci pass 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ciif sysconfig.get_config_vars().get('PY_CFLAGS', ''): 767db96d56Sopenharmony_ci BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS']) 777db96d56Sopenharmony_cielse: 787db96d56Sopenharmony_ci # Usually, sys.gettotalrefcount() is only present if Python has been 797db96d56Sopenharmony_ci # compiled in debug mode. If it's missing, expect that Python has 807db96d56Sopenharmony_ci # been released in release mode: with NDEBUG defined. 817db96d56Sopenharmony_ci BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount')) 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_ci### Tests 847db96d56Sopenharmony_ci############################################################################### 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ciclass GCTests(unittest.TestCase): 877db96d56Sopenharmony_ci def test_list(self): 887db96d56Sopenharmony_ci l = [] 897db96d56Sopenharmony_ci l.append(l) 907db96d56Sopenharmony_ci gc.collect() 917db96d56Sopenharmony_ci del l 927db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 1) 937db96d56Sopenharmony_ci 947db96d56Sopenharmony_ci def test_dict(self): 957db96d56Sopenharmony_ci d = {} 967db96d56Sopenharmony_ci d[1] = d 977db96d56Sopenharmony_ci gc.collect() 987db96d56Sopenharmony_ci del d 997db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 1) 1007db96d56Sopenharmony_ci 1017db96d56Sopenharmony_ci def test_tuple(self): 1027db96d56Sopenharmony_ci # since tuples are immutable we close the loop with a list 1037db96d56Sopenharmony_ci l = [] 1047db96d56Sopenharmony_ci t = (l,) 1057db96d56Sopenharmony_ci l.append(t) 1067db96d56Sopenharmony_ci gc.collect() 1077db96d56Sopenharmony_ci del t 1087db96d56Sopenharmony_ci del l 1097db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci def test_class(self): 1127db96d56Sopenharmony_ci class A: 1137db96d56Sopenharmony_ci pass 1147db96d56Sopenharmony_ci A.a = A 1157db96d56Sopenharmony_ci gc.collect() 1167db96d56Sopenharmony_ci del A 1177db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1187db96d56Sopenharmony_ci 1197db96d56Sopenharmony_ci def test_newstyleclass(self): 1207db96d56Sopenharmony_ci class A(object): 1217db96d56Sopenharmony_ci pass 1227db96d56Sopenharmony_ci gc.collect() 1237db96d56Sopenharmony_ci del A 1247db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci def test_instance(self): 1277db96d56Sopenharmony_ci class A: 1287db96d56Sopenharmony_ci pass 1297db96d56Sopenharmony_ci a = A() 1307db96d56Sopenharmony_ci a.a = a 1317db96d56Sopenharmony_ci gc.collect() 1327db96d56Sopenharmony_ci del a 1337db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1347db96d56Sopenharmony_ci 1357db96d56Sopenharmony_ci def test_newinstance(self): 1367db96d56Sopenharmony_ci class A(object): 1377db96d56Sopenharmony_ci pass 1387db96d56Sopenharmony_ci a = A() 1397db96d56Sopenharmony_ci a.a = a 1407db96d56Sopenharmony_ci gc.collect() 1417db96d56Sopenharmony_ci del a 1427db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1437db96d56Sopenharmony_ci class B(list): 1447db96d56Sopenharmony_ci pass 1457db96d56Sopenharmony_ci class C(B, A): 1467db96d56Sopenharmony_ci pass 1477db96d56Sopenharmony_ci a = C() 1487db96d56Sopenharmony_ci a.a = a 1497db96d56Sopenharmony_ci gc.collect() 1507db96d56Sopenharmony_ci del a 1517db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1527db96d56Sopenharmony_ci del B, C 1537db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1547db96d56Sopenharmony_ci A.a = A() 1557db96d56Sopenharmony_ci del A 1567db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1577db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 0) 1587db96d56Sopenharmony_ci 1597db96d56Sopenharmony_ci def test_method(self): 1607db96d56Sopenharmony_ci # Tricky: self.__init__ is a bound method, it references the instance. 1617db96d56Sopenharmony_ci class A: 1627db96d56Sopenharmony_ci def __init__(self): 1637db96d56Sopenharmony_ci self.init = self.__init__ 1647db96d56Sopenharmony_ci a = A() 1657db96d56Sopenharmony_ci gc.collect() 1667db96d56Sopenharmony_ci del a 1677db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci @cpython_only 1707db96d56Sopenharmony_ci def test_legacy_finalizer(self): 1717db96d56Sopenharmony_ci # A() is uncollectable if it is part of a cycle, make sure it shows up 1727db96d56Sopenharmony_ci # in gc.garbage. 1737db96d56Sopenharmony_ci @with_tp_del 1747db96d56Sopenharmony_ci class A: 1757db96d56Sopenharmony_ci def __tp_del__(self): pass 1767db96d56Sopenharmony_ci class B: 1777db96d56Sopenharmony_ci pass 1787db96d56Sopenharmony_ci a = A() 1797db96d56Sopenharmony_ci a.a = a 1807db96d56Sopenharmony_ci id_a = id(a) 1817db96d56Sopenharmony_ci b = B() 1827db96d56Sopenharmony_ci b.b = b 1837db96d56Sopenharmony_ci gc.collect() 1847db96d56Sopenharmony_ci del a 1857db96d56Sopenharmony_ci del b 1867db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 1877db96d56Sopenharmony_ci for obj in gc.garbage: 1887db96d56Sopenharmony_ci if id(obj) == id_a: 1897db96d56Sopenharmony_ci del obj.a 1907db96d56Sopenharmony_ci break 1917db96d56Sopenharmony_ci else: 1927db96d56Sopenharmony_ci self.fail("didn't find obj in garbage (finalizer)") 1937db96d56Sopenharmony_ci gc.garbage.remove(obj) 1947db96d56Sopenharmony_ci 1957db96d56Sopenharmony_ci @cpython_only 1967db96d56Sopenharmony_ci def test_legacy_finalizer_newclass(self): 1977db96d56Sopenharmony_ci # A() is uncollectable if it is part of a cycle, make sure it shows up 1987db96d56Sopenharmony_ci # in gc.garbage. 1997db96d56Sopenharmony_ci @with_tp_del 2007db96d56Sopenharmony_ci class A(object): 2017db96d56Sopenharmony_ci def __tp_del__(self): pass 2027db96d56Sopenharmony_ci class B(object): 2037db96d56Sopenharmony_ci pass 2047db96d56Sopenharmony_ci a = A() 2057db96d56Sopenharmony_ci a.a = a 2067db96d56Sopenharmony_ci id_a = id(a) 2077db96d56Sopenharmony_ci b = B() 2087db96d56Sopenharmony_ci b.b = b 2097db96d56Sopenharmony_ci gc.collect() 2107db96d56Sopenharmony_ci del a 2117db96d56Sopenharmony_ci del b 2127db96d56Sopenharmony_ci self.assertNotEqual(gc.collect(), 0) 2137db96d56Sopenharmony_ci for obj in gc.garbage: 2147db96d56Sopenharmony_ci if id(obj) == id_a: 2157db96d56Sopenharmony_ci del obj.a 2167db96d56Sopenharmony_ci break 2177db96d56Sopenharmony_ci else: 2187db96d56Sopenharmony_ci self.fail("didn't find obj in garbage (finalizer)") 2197db96d56Sopenharmony_ci gc.garbage.remove(obj) 2207db96d56Sopenharmony_ci 2217db96d56Sopenharmony_ci def test_function(self): 2227db96d56Sopenharmony_ci # Tricky: f -> d -> f, code should call d.clear() after the exec to 2237db96d56Sopenharmony_ci # break the cycle. 2247db96d56Sopenharmony_ci d = {} 2257db96d56Sopenharmony_ci exec("def f(): pass\n", d) 2267db96d56Sopenharmony_ci gc.collect() 2277db96d56Sopenharmony_ci del d 2287db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 2297db96d56Sopenharmony_ci 2307db96d56Sopenharmony_ci def test_function_tp_clear_leaves_consistent_state(self): 2317db96d56Sopenharmony_ci # https://github.com/python/cpython/issues/91636 2327db96d56Sopenharmony_ci code = """if 1: 2337db96d56Sopenharmony_ci 2347db96d56Sopenharmony_ci import gc 2357db96d56Sopenharmony_ci import weakref 2367db96d56Sopenharmony_ci 2377db96d56Sopenharmony_ci class LateFin: 2387db96d56Sopenharmony_ci __slots__ = ('ref',) 2397db96d56Sopenharmony_ci 2407db96d56Sopenharmony_ci def __del__(self): 2417db96d56Sopenharmony_ci 2427db96d56Sopenharmony_ci # 8. Now `latefin`'s finalizer is called. Here we 2437db96d56Sopenharmony_ci # obtain a reference to `func`, which is currently 2447db96d56Sopenharmony_ci # undergoing `tp_clear`. 2457db96d56Sopenharmony_ci global func 2467db96d56Sopenharmony_ci func = self.ref() 2477db96d56Sopenharmony_ci 2487db96d56Sopenharmony_ci class Cyclic(tuple): 2497db96d56Sopenharmony_ci __slots__ = () 2507db96d56Sopenharmony_ci 2517db96d56Sopenharmony_ci # 4. The finalizers of all garbage objects are called. In 2527db96d56Sopenharmony_ci # this case this is only us as `func` doesn't have a 2537db96d56Sopenharmony_ci # finalizer. 2547db96d56Sopenharmony_ci def __del__(self): 2557db96d56Sopenharmony_ci 2567db96d56Sopenharmony_ci # 5. Create a weakref to `func` now. If we had created 2577db96d56Sopenharmony_ci # it earlier, it would have been cleared by the 2587db96d56Sopenharmony_ci # garbage collector before calling the finalizers. 2597db96d56Sopenharmony_ci self[1].ref = weakref.ref(self[0]) 2607db96d56Sopenharmony_ci 2617db96d56Sopenharmony_ci # 6. Drop the global reference to `latefin`. The only 2627db96d56Sopenharmony_ci # remaining reference is the one we have. 2637db96d56Sopenharmony_ci global latefin 2647db96d56Sopenharmony_ci del latefin 2657db96d56Sopenharmony_ci 2667db96d56Sopenharmony_ci # 7. Now `func` is `tp_clear`-ed. This drops the last 2677db96d56Sopenharmony_ci # reference to `Cyclic`, which gets `tp_dealloc`-ed. 2687db96d56Sopenharmony_ci # This drops the last reference to `latefin`. 2697db96d56Sopenharmony_ci 2707db96d56Sopenharmony_ci latefin = LateFin() 2717db96d56Sopenharmony_ci def func(): 2727db96d56Sopenharmony_ci pass 2737db96d56Sopenharmony_ci cyc = tuple.__new__(Cyclic, (func, latefin)) 2747db96d56Sopenharmony_ci 2757db96d56Sopenharmony_ci # 1. Create a reference cycle of `cyc` and `func`. 2767db96d56Sopenharmony_ci func.__module__ = cyc 2777db96d56Sopenharmony_ci 2787db96d56Sopenharmony_ci # 2. Make the cycle unreachable, but keep the global reference 2797db96d56Sopenharmony_ci # to `latefin` so that it isn't detected as garbage. This 2807db96d56Sopenharmony_ci # way its finalizer will not be called immediately. 2817db96d56Sopenharmony_ci del func, cyc 2827db96d56Sopenharmony_ci 2837db96d56Sopenharmony_ci # 3. Invoke garbage collection, 2847db96d56Sopenharmony_ci # which will find `cyc` and `func` as garbage. 2857db96d56Sopenharmony_ci gc.collect() 2867db96d56Sopenharmony_ci 2877db96d56Sopenharmony_ci # 9. Previously, this would crash because `func_qualname` 2887db96d56Sopenharmony_ci # had been NULL-ed out by func_clear(). 2897db96d56Sopenharmony_ci print(f"{func=}") 2907db96d56Sopenharmony_ci """ 2917db96d56Sopenharmony_ci # We're mostly just checking that this doesn't crash. 2927db96d56Sopenharmony_ci rc, stdout, stderr = assert_python_ok("-c", code) 2937db96d56Sopenharmony_ci self.assertEqual(rc, 0) 2947db96d56Sopenharmony_ci self.assertRegex(stdout, rb"""\A\s*func=<function at \S+>\s*\Z""") 2957db96d56Sopenharmony_ci self.assertFalse(stderr) 2967db96d56Sopenharmony_ci 2977db96d56Sopenharmony_ci @refcount_test 2987db96d56Sopenharmony_ci def test_frame(self): 2997db96d56Sopenharmony_ci def f(): 3007db96d56Sopenharmony_ci frame = sys._getframe() 3017db96d56Sopenharmony_ci gc.collect() 3027db96d56Sopenharmony_ci f() 3037db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 1) 3047db96d56Sopenharmony_ci 3057db96d56Sopenharmony_ci def test_saveall(self): 3067db96d56Sopenharmony_ci # Verify that cyclic garbage like lists show up in gc.garbage if the 3077db96d56Sopenharmony_ci # SAVEALL option is enabled. 3087db96d56Sopenharmony_ci 3097db96d56Sopenharmony_ci # First make sure we don't save away other stuff that just happens to 3107db96d56Sopenharmony_ci # be waiting for collection. 3117db96d56Sopenharmony_ci gc.collect() 3127db96d56Sopenharmony_ci # if this fails, someone else created immortal trash 3137db96d56Sopenharmony_ci self.assertEqual(gc.garbage, []) 3147db96d56Sopenharmony_ci 3157db96d56Sopenharmony_ci L = [] 3167db96d56Sopenharmony_ci L.append(L) 3177db96d56Sopenharmony_ci id_L = id(L) 3187db96d56Sopenharmony_ci 3197db96d56Sopenharmony_ci debug = gc.get_debug() 3207db96d56Sopenharmony_ci gc.set_debug(debug | gc.DEBUG_SAVEALL) 3217db96d56Sopenharmony_ci del L 3227db96d56Sopenharmony_ci gc.collect() 3237db96d56Sopenharmony_ci gc.set_debug(debug) 3247db96d56Sopenharmony_ci 3257db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), 1) 3267db96d56Sopenharmony_ci obj = gc.garbage.pop() 3277db96d56Sopenharmony_ci self.assertEqual(id(obj), id_L) 3287db96d56Sopenharmony_ci 3297db96d56Sopenharmony_ci def test_del(self): 3307db96d56Sopenharmony_ci # __del__ methods can trigger collection, make this to happen 3317db96d56Sopenharmony_ci thresholds = gc.get_threshold() 3327db96d56Sopenharmony_ci gc.enable() 3337db96d56Sopenharmony_ci gc.set_threshold(1) 3347db96d56Sopenharmony_ci 3357db96d56Sopenharmony_ci class A: 3367db96d56Sopenharmony_ci def __del__(self): 3377db96d56Sopenharmony_ci dir(self) 3387db96d56Sopenharmony_ci a = A() 3397db96d56Sopenharmony_ci del a 3407db96d56Sopenharmony_ci 3417db96d56Sopenharmony_ci gc.disable() 3427db96d56Sopenharmony_ci gc.set_threshold(*thresholds) 3437db96d56Sopenharmony_ci 3447db96d56Sopenharmony_ci def test_del_newclass(self): 3457db96d56Sopenharmony_ci # __del__ methods can trigger collection, make this to happen 3467db96d56Sopenharmony_ci thresholds = gc.get_threshold() 3477db96d56Sopenharmony_ci gc.enable() 3487db96d56Sopenharmony_ci gc.set_threshold(1) 3497db96d56Sopenharmony_ci 3507db96d56Sopenharmony_ci class A(object): 3517db96d56Sopenharmony_ci def __del__(self): 3527db96d56Sopenharmony_ci dir(self) 3537db96d56Sopenharmony_ci a = A() 3547db96d56Sopenharmony_ci del a 3557db96d56Sopenharmony_ci 3567db96d56Sopenharmony_ci gc.disable() 3577db96d56Sopenharmony_ci gc.set_threshold(*thresholds) 3587db96d56Sopenharmony_ci 3597db96d56Sopenharmony_ci # The following two tests are fragile: 3607db96d56Sopenharmony_ci # They precisely count the number of allocations, 3617db96d56Sopenharmony_ci # which is highly implementation-dependent. 3627db96d56Sopenharmony_ci # For example, disposed tuples are not freed, but reused. 3637db96d56Sopenharmony_ci # To minimize variations, though, we first store the get_count() results 3647db96d56Sopenharmony_ci # and check them at the end. 3657db96d56Sopenharmony_ci @refcount_test 3667db96d56Sopenharmony_ci def test_get_count(self): 3677db96d56Sopenharmony_ci gc.collect() 3687db96d56Sopenharmony_ci a, b, c = gc.get_count() 3697db96d56Sopenharmony_ci x = [] 3707db96d56Sopenharmony_ci d, e, f = gc.get_count() 3717db96d56Sopenharmony_ci self.assertEqual((b, c), (0, 0)) 3727db96d56Sopenharmony_ci self.assertEqual((e, f), (0, 0)) 3737db96d56Sopenharmony_ci # This is less fragile than asserting that a equals 0. 3747db96d56Sopenharmony_ci self.assertLess(a, 5) 3757db96d56Sopenharmony_ci # Between the two calls to get_count(), at least one object was 3767db96d56Sopenharmony_ci # created (the list). 3777db96d56Sopenharmony_ci self.assertGreater(d, a) 3787db96d56Sopenharmony_ci 3797db96d56Sopenharmony_ci @refcount_test 3807db96d56Sopenharmony_ci def test_collect_generations(self): 3817db96d56Sopenharmony_ci gc.collect() 3827db96d56Sopenharmony_ci # This object will "trickle" into generation N + 1 after 3837db96d56Sopenharmony_ci # each call to collect(N) 3847db96d56Sopenharmony_ci x = [] 3857db96d56Sopenharmony_ci gc.collect(0) 3867db96d56Sopenharmony_ci # x is now in gen 1 3877db96d56Sopenharmony_ci a, b, c = gc.get_count() 3887db96d56Sopenharmony_ci gc.collect(1) 3897db96d56Sopenharmony_ci # x is now in gen 2 3907db96d56Sopenharmony_ci d, e, f = gc.get_count() 3917db96d56Sopenharmony_ci gc.collect(2) 3927db96d56Sopenharmony_ci # x is now in gen 3 3937db96d56Sopenharmony_ci g, h, i = gc.get_count() 3947db96d56Sopenharmony_ci # We don't check a, d, g since their exact values depends on 3957db96d56Sopenharmony_ci # internal implementation details of the interpreter. 3967db96d56Sopenharmony_ci self.assertEqual((b, c), (1, 0)) 3977db96d56Sopenharmony_ci self.assertEqual((e, f), (0, 1)) 3987db96d56Sopenharmony_ci self.assertEqual((h, i), (0, 0)) 3997db96d56Sopenharmony_ci 4007db96d56Sopenharmony_ci def test_trashcan(self): 4017db96d56Sopenharmony_ci class Ouch: 4027db96d56Sopenharmony_ci n = 0 4037db96d56Sopenharmony_ci def __del__(self): 4047db96d56Sopenharmony_ci Ouch.n = Ouch.n + 1 4057db96d56Sopenharmony_ci if Ouch.n % 17 == 0: 4067db96d56Sopenharmony_ci gc.collect() 4077db96d56Sopenharmony_ci 4087db96d56Sopenharmony_ci # "trashcan" is a hack to prevent stack overflow when deallocating 4097db96d56Sopenharmony_ci # very deeply nested tuples etc. It works in part by abusing the 4107db96d56Sopenharmony_ci # type pointer and refcount fields, and that can yield horrible 4117db96d56Sopenharmony_ci # problems when gc tries to traverse the structures. 4127db96d56Sopenharmony_ci # If this test fails (as it does in 2.0, 2.1 and 2.2), it will 4137db96d56Sopenharmony_ci # most likely die via segfault. 4147db96d56Sopenharmony_ci 4157db96d56Sopenharmony_ci # Note: In 2.3 the possibility for compiling without cyclic gc was 4167db96d56Sopenharmony_ci # removed, and that in turn allows the trashcan mechanism to work 4177db96d56Sopenharmony_ci # via much simpler means (e.g., it never abuses the type pointer or 4187db96d56Sopenharmony_ci # refcount fields anymore). Since it's much less likely to cause a 4197db96d56Sopenharmony_ci # problem now, the various constants in this expensive (we force a lot 4207db96d56Sopenharmony_ci # of full collections) test are cut back from the 2.2 version. 4217db96d56Sopenharmony_ci gc.enable() 4227db96d56Sopenharmony_ci N = 150 4237db96d56Sopenharmony_ci for count in range(2): 4247db96d56Sopenharmony_ci t = [] 4257db96d56Sopenharmony_ci for i in range(N): 4267db96d56Sopenharmony_ci t = [t, Ouch()] 4277db96d56Sopenharmony_ci u = [] 4287db96d56Sopenharmony_ci for i in range(N): 4297db96d56Sopenharmony_ci u = [u, Ouch()] 4307db96d56Sopenharmony_ci v = {} 4317db96d56Sopenharmony_ci for i in range(N): 4327db96d56Sopenharmony_ci v = {1: v, 2: Ouch()} 4337db96d56Sopenharmony_ci gc.disable() 4347db96d56Sopenharmony_ci 4357db96d56Sopenharmony_ci @threading_helper.requires_working_threading() 4367db96d56Sopenharmony_ci def test_trashcan_threads(self): 4377db96d56Sopenharmony_ci # Issue #13992: trashcan mechanism should be thread-safe 4387db96d56Sopenharmony_ci NESTING = 60 4397db96d56Sopenharmony_ci N_THREADS = 2 4407db96d56Sopenharmony_ci 4417db96d56Sopenharmony_ci def sleeper_gen(): 4427db96d56Sopenharmony_ci """A generator that releases the GIL when closed or dealloc'ed.""" 4437db96d56Sopenharmony_ci try: 4447db96d56Sopenharmony_ci yield 4457db96d56Sopenharmony_ci finally: 4467db96d56Sopenharmony_ci time.sleep(0.000001) 4477db96d56Sopenharmony_ci 4487db96d56Sopenharmony_ci class C(list): 4497db96d56Sopenharmony_ci # Appending to a list is atomic, which avoids the use of a lock. 4507db96d56Sopenharmony_ci inits = [] 4517db96d56Sopenharmony_ci dels = [] 4527db96d56Sopenharmony_ci def __init__(self, alist): 4537db96d56Sopenharmony_ci self[:] = alist 4547db96d56Sopenharmony_ci C.inits.append(None) 4557db96d56Sopenharmony_ci def __del__(self): 4567db96d56Sopenharmony_ci # This __del__ is called by subtype_dealloc(). 4577db96d56Sopenharmony_ci C.dels.append(None) 4587db96d56Sopenharmony_ci # `g` will release the GIL when garbage-collected. This 4597db96d56Sopenharmony_ci # helps assert subtype_dealloc's behaviour when threads 4607db96d56Sopenharmony_ci # switch in the middle of it. 4617db96d56Sopenharmony_ci g = sleeper_gen() 4627db96d56Sopenharmony_ci next(g) 4637db96d56Sopenharmony_ci # Now that __del__ is finished, subtype_dealloc will proceed 4647db96d56Sopenharmony_ci # to call list_dealloc, which also uses the trashcan mechanism. 4657db96d56Sopenharmony_ci 4667db96d56Sopenharmony_ci def make_nested(): 4677db96d56Sopenharmony_ci """Create a sufficiently nested container object so that the 4687db96d56Sopenharmony_ci trashcan mechanism is invoked when deallocating it.""" 4697db96d56Sopenharmony_ci x = C([]) 4707db96d56Sopenharmony_ci for i in range(NESTING): 4717db96d56Sopenharmony_ci x = [C([x])] 4727db96d56Sopenharmony_ci del x 4737db96d56Sopenharmony_ci 4747db96d56Sopenharmony_ci def run_thread(): 4757db96d56Sopenharmony_ci """Exercise make_nested() in a loop.""" 4767db96d56Sopenharmony_ci while not exit: 4777db96d56Sopenharmony_ci make_nested() 4787db96d56Sopenharmony_ci 4797db96d56Sopenharmony_ci old_switchinterval = sys.getswitchinterval() 4807db96d56Sopenharmony_ci sys.setswitchinterval(1e-5) 4817db96d56Sopenharmony_ci try: 4827db96d56Sopenharmony_ci exit = [] 4837db96d56Sopenharmony_ci threads = [] 4847db96d56Sopenharmony_ci for i in range(N_THREADS): 4857db96d56Sopenharmony_ci t = threading.Thread(target=run_thread) 4867db96d56Sopenharmony_ci threads.append(t) 4877db96d56Sopenharmony_ci with threading_helper.start_threads(threads, lambda: exit.append(1)): 4887db96d56Sopenharmony_ci time.sleep(1.0) 4897db96d56Sopenharmony_ci finally: 4907db96d56Sopenharmony_ci sys.setswitchinterval(old_switchinterval) 4917db96d56Sopenharmony_ci gc.collect() 4927db96d56Sopenharmony_ci self.assertEqual(len(C.inits), len(C.dels)) 4937db96d56Sopenharmony_ci 4947db96d56Sopenharmony_ci def test_boom(self): 4957db96d56Sopenharmony_ci class Boom: 4967db96d56Sopenharmony_ci def __getattr__(self, someattribute): 4977db96d56Sopenharmony_ci del self.attr 4987db96d56Sopenharmony_ci raise AttributeError 4997db96d56Sopenharmony_ci 5007db96d56Sopenharmony_ci a = Boom() 5017db96d56Sopenharmony_ci b = Boom() 5027db96d56Sopenharmony_ci a.attr = b 5037db96d56Sopenharmony_ci b.attr = a 5047db96d56Sopenharmony_ci 5057db96d56Sopenharmony_ci gc.collect() 5067db96d56Sopenharmony_ci garbagelen = len(gc.garbage) 5077db96d56Sopenharmony_ci del a, b 5087db96d56Sopenharmony_ci # a<->b are in a trash cycle now. Collection will invoke 5097db96d56Sopenharmony_ci # Boom.__getattr__ (to see whether a and b have __del__ methods), and 5107db96d56Sopenharmony_ci # __getattr__ deletes the internal "attr" attributes as a side effect. 5117db96d56Sopenharmony_ci # That causes the trash cycle to get reclaimed via refcounts falling to 5127db96d56Sopenharmony_ci # 0, thus mutating the trash graph as a side effect of merely asking 5137db96d56Sopenharmony_ci # whether __del__ exists. This used to (before 2.3b1) crash Python. 5147db96d56Sopenharmony_ci # Now __getattr__ isn't called. 5157db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 5167db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), garbagelen) 5177db96d56Sopenharmony_ci 5187db96d56Sopenharmony_ci def test_boom2(self): 5197db96d56Sopenharmony_ci class Boom2: 5207db96d56Sopenharmony_ci def __init__(self): 5217db96d56Sopenharmony_ci self.x = 0 5227db96d56Sopenharmony_ci 5237db96d56Sopenharmony_ci def __getattr__(self, someattribute): 5247db96d56Sopenharmony_ci self.x += 1 5257db96d56Sopenharmony_ci if self.x > 1: 5267db96d56Sopenharmony_ci del self.attr 5277db96d56Sopenharmony_ci raise AttributeError 5287db96d56Sopenharmony_ci 5297db96d56Sopenharmony_ci a = Boom2() 5307db96d56Sopenharmony_ci b = Boom2() 5317db96d56Sopenharmony_ci a.attr = b 5327db96d56Sopenharmony_ci b.attr = a 5337db96d56Sopenharmony_ci 5347db96d56Sopenharmony_ci gc.collect() 5357db96d56Sopenharmony_ci garbagelen = len(gc.garbage) 5367db96d56Sopenharmony_ci del a, b 5377db96d56Sopenharmony_ci # Much like test_boom(), except that __getattr__ doesn't break the 5387db96d56Sopenharmony_ci # cycle until the second time gc checks for __del__. As of 2.3b1, 5397db96d56Sopenharmony_ci # there isn't a second time, so this simply cleans up the trash cycle. 5407db96d56Sopenharmony_ci # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get 5417db96d56Sopenharmony_ci # reclaimed this way. 5427db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 5437db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), garbagelen) 5447db96d56Sopenharmony_ci 5457db96d56Sopenharmony_ci def test_boom_new(self): 5467db96d56Sopenharmony_ci # boom__new and boom2_new are exactly like boom and boom2, except use 5477db96d56Sopenharmony_ci # new-style classes. 5487db96d56Sopenharmony_ci 5497db96d56Sopenharmony_ci class Boom_New(object): 5507db96d56Sopenharmony_ci def __getattr__(self, someattribute): 5517db96d56Sopenharmony_ci del self.attr 5527db96d56Sopenharmony_ci raise AttributeError 5537db96d56Sopenharmony_ci 5547db96d56Sopenharmony_ci a = Boom_New() 5557db96d56Sopenharmony_ci b = Boom_New() 5567db96d56Sopenharmony_ci a.attr = b 5577db96d56Sopenharmony_ci b.attr = a 5587db96d56Sopenharmony_ci 5597db96d56Sopenharmony_ci gc.collect() 5607db96d56Sopenharmony_ci garbagelen = len(gc.garbage) 5617db96d56Sopenharmony_ci del a, b 5627db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 5637db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), garbagelen) 5647db96d56Sopenharmony_ci 5657db96d56Sopenharmony_ci def test_boom2_new(self): 5667db96d56Sopenharmony_ci class Boom2_New(object): 5677db96d56Sopenharmony_ci def __init__(self): 5687db96d56Sopenharmony_ci self.x = 0 5697db96d56Sopenharmony_ci 5707db96d56Sopenharmony_ci def __getattr__(self, someattribute): 5717db96d56Sopenharmony_ci self.x += 1 5727db96d56Sopenharmony_ci if self.x > 1: 5737db96d56Sopenharmony_ci del self.attr 5747db96d56Sopenharmony_ci raise AttributeError 5757db96d56Sopenharmony_ci 5767db96d56Sopenharmony_ci a = Boom2_New() 5777db96d56Sopenharmony_ci b = Boom2_New() 5787db96d56Sopenharmony_ci a.attr = b 5797db96d56Sopenharmony_ci b.attr = a 5807db96d56Sopenharmony_ci 5817db96d56Sopenharmony_ci gc.collect() 5827db96d56Sopenharmony_ci garbagelen = len(gc.garbage) 5837db96d56Sopenharmony_ci del a, b 5847db96d56Sopenharmony_ci self.assertEqual(gc.collect(), 2) 5857db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), garbagelen) 5867db96d56Sopenharmony_ci 5877db96d56Sopenharmony_ci def test_get_referents(self): 5887db96d56Sopenharmony_ci alist = [1, 3, 5] 5897db96d56Sopenharmony_ci got = gc.get_referents(alist) 5907db96d56Sopenharmony_ci got.sort() 5917db96d56Sopenharmony_ci self.assertEqual(got, alist) 5927db96d56Sopenharmony_ci 5937db96d56Sopenharmony_ci atuple = tuple(alist) 5947db96d56Sopenharmony_ci got = gc.get_referents(atuple) 5957db96d56Sopenharmony_ci got.sort() 5967db96d56Sopenharmony_ci self.assertEqual(got, alist) 5977db96d56Sopenharmony_ci 5987db96d56Sopenharmony_ci adict = {1: 3, 5: 7} 5997db96d56Sopenharmony_ci expected = [1, 3, 5, 7] 6007db96d56Sopenharmony_ci got = gc.get_referents(adict) 6017db96d56Sopenharmony_ci got.sort() 6027db96d56Sopenharmony_ci self.assertEqual(got, expected) 6037db96d56Sopenharmony_ci 6047db96d56Sopenharmony_ci got = gc.get_referents([1, 2], {3: 4}, (0, 0, 0)) 6057db96d56Sopenharmony_ci got.sort() 6067db96d56Sopenharmony_ci self.assertEqual(got, [0, 0] + list(range(5))) 6077db96d56Sopenharmony_ci 6087db96d56Sopenharmony_ci self.assertEqual(gc.get_referents(1, 'a', 4j), []) 6097db96d56Sopenharmony_ci 6107db96d56Sopenharmony_ci def test_is_tracked(self): 6117db96d56Sopenharmony_ci # Atomic built-in types are not tracked, user-defined objects and 6127db96d56Sopenharmony_ci # mutable containers are. 6137db96d56Sopenharmony_ci # NOTE: types with special optimizations (e.g. tuple) have tests 6147db96d56Sopenharmony_ci # in their own test files instead. 6157db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(None)) 6167db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(1)) 6177db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(1.0)) 6187db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(1.0 + 5.0j)) 6197db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(True)) 6207db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(False)) 6217db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(b"a")) 6227db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked("a")) 6237db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(bytearray(b"a"))) 6247db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(type)) 6257db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(int)) 6267db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(object)) 6277db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(object())) 6287db96d56Sopenharmony_ci 6297db96d56Sopenharmony_ci class UserClass: 6307db96d56Sopenharmony_ci pass 6317db96d56Sopenharmony_ci 6327db96d56Sopenharmony_ci class UserInt(int): 6337db96d56Sopenharmony_ci pass 6347db96d56Sopenharmony_ci 6357db96d56Sopenharmony_ci # Base class is object; no extra fields. 6367db96d56Sopenharmony_ci class UserClassSlots: 6377db96d56Sopenharmony_ci __slots__ = () 6387db96d56Sopenharmony_ci 6397db96d56Sopenharmony_ci # Base class is fixed size larger than object; no extra fields. 6407db96d56Sopenharmony_ci class UserFloatSlots(float): 6417db96d56Sopenharmony_ci __slots__ = () 6427db96d56Sopenharmony_ci 6437db96d56Sopenharmony_ci # Base class is variable size; no extra fields. 6447db96d56Sopenharmony_ci class UserIntSlots(int): 6457db96d56Sopenharmony_ci __slots__ = () 6467db96d56Sopenharmony_ci 6477db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(gc)) 6487db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserClass)) 6497db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserClass())) 6507db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserInt())) 6517db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked([])) 6527db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(set())) 6537db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserClassSlots())) 6547db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserFloatSlots())) 6557db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(UserIntSlots())) 6567db96d56Sopenharmony_ci 6577db96d56Sopenharmony_ci def test_is_finalized(self): 6587db96d56Sopenharmony_ci # Objects not tracked by the always gc return false 6597db96d56Sopenharmony_ci self.assertFalse(gc.is_finalized(3)) 6607db96d56Sopenharmony_ci 6617db96d56Sopenharmony_ci storage = [] 6627db96d56Sopenharmony_ci class Lazarus: 6637db96d56Sopenharmony_ci def __del__(self): 6647db96d56Sopenharmony_ci storage.append(self) 6657db96d56Sopenharmony_ci 6667db96d56Sopenharmony_ci lazarus = Lazarus() 6677db96d56Sopenharmony_ci self.assertFalse(gc.is_finalized(lazarus)) 6687db96d56Sopenharmony_ci 6697db96d56Sopenharmony_ci del lazarus 6707db96d56Sopenharmony_ci gc.collect() 6717db96d56Sopenharmony_ci 6727db96d56Sopenharmony_ci lazarus = storage.pop() 6737db96d56Sopenharmony_ci self.assertTrue(gc.is_finalized(lazarus)) 6747db96d56Sopenharmony_ci 6757db96d56Sopenharmony_ci def test_bug1055820b(self): 6767db96d56Sopenharmony_ci # Corresponds to temp2b.py in the bug report. 6777db96d56Sopenharmony_ci 6787db96d56Sopenharmony_ci ouch = [] 6797db96d56Sopenharmony_ci def callback(ignored): 6807db96d56Sopenharmony_ci ouch[:] = [wr() for wr in WRs] 6817db96d56Sopenharmony_ci 6827db96d56Sopenharmony_ci Cs = [C1055820(i) for i in range(2)] 6837db96d56Sopenharmony_ci WRs = [weakref.ref(c, callback) for c in Cs] 6847db96d56Sopenharmony_ci c = None 6857db96d56Sopenharmony_ci 6867db96d56Sopenharmony_ci gc.collect() 6877db96d56Sopenharmony_ci self.assertEqual(len(ouch), 0) 6887db96d56Sopenharmony_ci # Make the two instances trash, and collect again. The bug was that 6897db96d56Sopenharmony_ci # the callback materialized a strong reference to an instance, but gc 6907db96d56Sopenharmony_ci # cleared the instance's dict anyway. 6917db96d56Sopenharmony_ci Cs = None 6927db96d56Sopenharmony_ci gc.collect() 6937db96d56Sopenharmony_ci self.assertEqual(len(ouch), 2) # else the callbacks didn't run 6947db96d56Sopenharmony_ci for x in ouch: 6957db96d56Sopenharmony_ci # If the callback resurrected one of these guys, the instance 6967db96d56Sopenharmony_ci # would be damaged, with an empty __dict__. 6977db96d56Sopenharmony_ci self.assertEqual(x, None) 6987db96d56Sopenharmony_ci 6997db96d56Sopenharmony_ci def test_bug21435(self): 7007db96d56Sopenharmony_ci # This is a poor test - its only virtue is that it happened to 7017db96d56Sopenharmony_ci # segfault on Tim's Windows box before the patch for 21435 was 7027db96d56Sopenharmony_ci # applied. That's a nasty bug relying on specific pieces of cyclic 7037db96d56Sopenharmony_ci # trash appearing in exactly the right order in finalize_garbage()'s 7047db96d56Sopenharmony_ci # input list. 7057db96d56Sopenharmony_ci # But there's no reliable way to force that order from Python code, 7067db96d56Sopenharmony_ci # so over time chances are good this test won't really be testing much 7077db96d56Sopenharmony_ci # of anything anymore. Still, if it blows up, there's _some_ 7087db96d56Sopenharmony_ci # problem ;-) 7097db96d56Sopenharmony_ci gc.collect() 7107db96d56Sopenharmony_ci 7117db96d56Sopenharmony_ci class A: 7127db96d56Sopenharmony_ci pass 7137db96d56Sopenharmony_ci 7147db96d56Sopenharmony_ci class B: 7157db96d56Sopenharmony_ci def __init__(self, x): 7167db96d56Sopenharmony_ci self.x = x 7177db96d56Sopenharmony_ci 7187db96d56Sopenharmony_ci def __del__(self): 7197db96d56Sopenharmony_ci self.attr = None 7207db96d56Sopenharmony_ci 7217db96d56Sopenharmony_ci def do_work(): 7227db96d56Sopenharmony_ci a = A() 7237db96d56Sopenharmony_ci b = B(A()) 7247db96d56Sopenharmony_ci 7257db96d56Sopenharmony_ci a.attr = b 7267db96d56Sopenharmony_ci b.attr = a 7277db96d56Sopenharmony_ci 7287db96d56Sopenharmony_ci do_work() 7297db96d56Sopenharmony_ci gc.collect() # this blows up (bad C pointer) when it fails 7307db96d56Sopenharmony_ci 7317db96d56Sopenharmony_ci @cpython_only 7327db96d56Sopenharmony_ci @requires_subprocess() 7337db96d56Sopenharmony_ci def test_garbage_at_shutdown(self): 7347db96d56Sopenharmony_ci import subprocess 7357db96d56Sopenharmony_ci code = """if 1: 7367db96d56Sopenharmony_ci import gc 7377db96d56Sopenharmony_ci import _testcapi 7387db96d56Sopenharmony_ci @_testcapi.with_tp_del 7397db96d56Sopenharmony_ci class X: 7407db96d56Sopenharmony_ci def __init__(self, name): 7417db96d56Sopenharmony_ci self.name = name 7427db96d56Sopenharmony_ci def __repr__(self): 7437db96d56Sopenharmony_ci return "<X %%r>" %% self.name 7447db96d56Sopenharmony_ci def __tp_del__(self): 7457db96d56Sopenharmony_ci pass 7467db96d56Sopenharmony_ci 7477db96d56Sopenharmony_ci x = X('first') 7487db96d56Sopenharmony_ci x.x = x 7497db96d56Sopenharmony_ci x.y = X('second') 7507db96d56Sopenharmony_ci del x 7517db96d56Sopenharmony_ci gc.set_debug(%s) 7527db96d56Sopenharmony_ci """ 7537db96d56Sopenharmony_ci def run_command(code): 7547db96d56Sopenharmony_ci p = subprocess.Popen([sys.executable, "-Wd", "-c", code], 7557db96d56Sopenharmony_ci stdout=subprocess.PIPE, 7567db96d56Sopenharmony_ci stderr=subprocess.PIPE) 7577db96d56Sopenharmony_ci stdout, stderr = p.communicate() 7587db96d56Sopenharmony_ci p.stdout.close() 7597db96d56Sopenharmony_ci p.stderr.close() 7607db96d56Sopenharmony_ci self.assertEqual(p.returncode, 0) 7617db96d56Sopenharmony_ci self.assertEqual(stdout, b"") 7627db96d56Sopenharmony_ci return stderr 7637db96d56Sopenharmony_ci 7647db96d56Sopenharmony_ci stderr = run_command(code % "0") 7657db96d56Sopenharmony_ci self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at " 7667db96d56Sopenharmony_ci b"shutdown; use", stderr) 7677db96d56Sopenharmony_ci self.assertNotIn(b"<X 'first'>", stderr) 7687db96d56Sopenharmony_ci # With DEBUG_UNCOLLECTABLE, the garbage list gets printed 7697db96d56Sopenharmony_ci stderr = run_command(code % "gc.DEBUG_UNCOLLECTABLE") 7707db96d56Sopenharmony_ci self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at " 7717db96d56Sopenharmony_ci b"shutdown", stderr) 7727db96d56Sopenharmony_ci self.assertTrue( 7737db96d56Sopenharmony_ci (b"[<X 'first'>, <X 'second'>]" in stderr) or 7747db96d56Sopenharmony_ci (b"[<X 'second'>, <X 'first'>]" in stderr), stderr) 7757db96d56Sopenharmony_ci # With DEBUG_SAVEALL, no additional message should get printed 7767db96d56Sopenharmony_ci # (because gc.garbage also contains normally reclaimable cyclic 7777db96d56Sopenharmony_ci # references, and its elements get printed at runtime anyway). 7787db96d56Sopenharmony_ci stderr = run_command(code % "gc.DEBUG_SAVEALL") 7797db96d56Sopenharmony_ci self.assertNotIn(b"uncollectable objects at shutdown", stderr) 7807db96d56Sopenharmony_ci 7817db96d56Sopenharmony_ci def test_gc_main_module_at_shutdown(self): 7827db96d56Sopenharmony_ci # Create a reference cycle through the __main__ module and check 7837db96d56Sopenharmony_ci # it gets collected at interpreter shutdown. 7847db96d56Sopenharmony_ci code = """if 1: 7857db96d56Sopenharmony_ci class C: 7867db96d56Sopenharmony_ci def __del__(self): 7877db96d56Sopenharmony_ci print('__del__ called') 7887db96d56Sopenharmony_ci l = [C()] 7897db96d56Sopenharmony_ci l.append(l) 7907db96d56Sopenharmony_ci """ 7917db96d56Sopenharmony_ci rc, out, err = assert_python_ok('-c', code) 7927db96d56Sopenharmony_ci self.assertEqual(out.strip(), b'__del__ called') 7937db96d56Sopenharmony_ci 7947db96d56Sopenharmony_ci def test_gc_ordinary_module_at_shutdown(self): 7957db96d56Sopenharmony_ci # Same as above, but with a non-__main__ module. 7967db96d56Sopenharmony_ci with temp_dir() as script_dir: 7977db96d56Sopenharmony_ci module = """if 1: 7987db96d56Sopenharmony_ci class C: 7997db96d56Sopenharmony_ci def __del__(self): 8007db96d56Sopenharmony_ci print('__del__ called') 8017db96d56Sopenharmony_ci l = [C()] 8027db96d56Sopenharmony_ci l.append(l) 8037db96d56Sopenharmony_ci """ 8047db96d56Sopenharmony_ci code = """if 1: 8057db96d56Sopenharmony_ci import sys 8067db96d56Sopenharmony_ci sys.path.insert(0, %r) 8077db96d56Sopenharmony_ci import gctest 8087db96d56Sopenharmony_ci """ % (script_dir,) 8097db96d56Sopenharmony_ci make_script(script_dir, 'gctest', module) 8107db96d56Sopenharmony_ci rc, out, err = assert_python_ok('-c', code) 8117db96d56Sopenharmony_ci self.assertEqual(out.strip(), b'__del__ called') 8127db96d56Sopenharmony_ci 8137db96d56Sopenharmony_ci def test_global_del_SystemExit(self): 8147db96d56Sopenharmony_ci code = """if 1: 8157db96d56Sopenharmony_ci class ClassWithDel: 8167db96d56Sopenharmony_ci def __del__(self): 8177db96d56Sopenharmony_ci print('__del__ called') 8187db96d56Sopenharmony_ci a = ClassWithDel() 8197db96d56Sopenharmony_ci a.link = a 8207db96d56Sopenharmony_ci raise SystemExit(0)""" 8217db96d56Sopenharmony_ci self.addCleanup(unlink, TESTFN) 8227db96d56Sopenharmony_ci with open(TESTFN, 'w', encoding="utf-8") as script: 8237db96d56Sopenharmony_ci script.write(code) 8247db96d56Sopenharmony_ci rc, out, err = assert_python_ok(TESTFN) 8257db96d56Sopenharmony_ci self.assertEqual(out.strip(), b'__del__ called') 8267db96d56Sopenharmony_ci 8277db96d56Sopenharmony_ci def test_get_stats(self): 8287db96d56Sopenharmony_ci stats = gc.get_stats() 8297db96d56Sopenharmony_ci self.assertEqual(len(stats), 3) 8307db96d56Sopenharmony_ci for st in stats: 8317db96d56Sopenharmony_ci self.assertIsInstance(st, dict) 8327db96d56Sopenharmony_ci self.assertEqual(set(st), 8337db96d56Sopenharmony_ci {"collected", "collections", "uncollectable"}) 8347db96d56Sopenharmony_ci self.assertGreaterEqual(st["collected"], 0) 8357db96d56Sopenharmony_ci self.assertGreaterEqual(st["collections"], 0) 8367db96d56Sopenharmony_ci self.assertGreaterEqual(st["uncollectable"], 0) 8377db96d56Sopenharmony_ci # Check that collection counts are incremented correctly 8387db96d56Sopenharmony_ci if gc.isenabled(): 8397db96d56Sopenharmony_ci self.addCleanup(gc.enable) 8407db96d56Sopenharmony_ci gc.disable() 8417db96d56Sopenharmony_ci old = gc.get_stats() 8427db96d56Sopenharmony_ci gc.collect(0) 8437db96d56Sopenharmony_ci new = gc.get_stats() 8447db96d56Sopenharmony_ci self.assertEqual(new[0]["collections"], old[0]["collections"] + 1) 8457db96d56Sopenharmony_ci self.assertEqual(new[1]["collections"], old[1]["collections"]) 8467db96d56Sopenharmony_ci self.assertEqual(new[2]["collections"], old[2]["collections"]) 8477db96d56Sopenharmony_ci gc.collect(2) 8487db96d56Sopenharmony_ci new = gc.get_stats() 8497db96d56Sopenharmony_ci self.assertEqual(new[0]["collections"], old[0]["collections"] + 1) 8507db96d56Sopenharmony_ci self.assertEqual(new[1]["collections"], old[1]["collections"]) 8517db96d56Sopenharmony_ci self.assertEqual(new[2]["collections"], old[2]["collections"] + 1) 8527db96d56Sopenharmony_ci 8537db96d56Sopenharmony_ci def test_freeze(self): 8547db96d56Sopenharmony_ci gc.freeze() 8557db96d56Sopenharmony_ci self.assertGreater(gc.get_freeze_count(), 0) 8567db96d56Sopenharmony_ci gc.unfreeze() 8577db96d56Sopenharmony_ci self.assertEqual(gc.get_freeze_count(), 0) 8587db96d56Sopenharmony_ci 8597db96d56Sopenharmony_ci def test_get_objects(self): 8607db96d56Sopenharmony_ci gc.collect() 8617db96d56Sopenharmony_ci l = [] 8627db96d56Sopenharmony_ci l.append(l) 8637db96d56Sopenharmony_ci self.assertTrue( 8647db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=0)) 8657db96d56Sopenharmony_ci ) 8667db96d56Sopenharmony_ci self.assertFalse( 8677db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=1)) 8687db96d56Sopenharmony_ci ) 8697db96d56Sopenharmony_ci self.assertFalse( 8707db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=2)) 8717db96d56Sopenharmony_ci ) 8727db96d56Sopenharmony_ci gc.collect(generation=0) 8737db96d56Sopenharmony_ci self.assertFalse( 8747db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=0)) 8757db96d56Sopenharmony_ci ) 8767db96d56Sopenharmony_ci self.assertTrue( 8777db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=1)) 8787db96d56Sopenharmony_ci ) 8797db96d56Sopenharmony_ci self.assertFalse( 8807db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=2)) 8817db96d56Sopenharmony_ci ) 8827db96d56Sopenharmony_ci gc.collect(generation=1) 8837db96d56Sopenharmony_ci self.assertFalse( 8847db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=0)) 8857db96d56Sopenharmony_ci ) 8867db96d56Sopenharmony_ci self.assertFalse( 8877db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=1)) 8887db96d56Sopenharmony_ci ) 8897db96d56Sopenharmony_ci self.assertTrue( 8907db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=2)) 8917db96d56Sopenharmony_ci ) 8927db96d56Sopenharmony_ci gc.collect(generation=2) 8937db96d56Sopenharmony_ci self.assertFalse( 8947db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=0)) 8957db96d56Sopenharmony_ci ) 8967db96d56Sopenharmony_ci self.assertFalse( 8977db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=1)) 8987db96d56Sopenharmony_ci ) 8997db96d56Sopenharmony_ci self.assertTrue( 9007db96d56Sopenharmony_ci any(l is element for element in gc.get_objects(generation=2)) 9017db96d56Sopenharmony_ci ) 9027db96d56Sopenharmony_ci del l 9037db96d56Sopenharmony_ci gc.collect() 9047db96d56Sopenharmony_ci 9057db96d56Sopenharmony_ci def test_get_objects_arguments(self): 9067db96d56Sopenharmony_ci gc.collect() 9077db96d56Sopenharmony_ci self.assertEqual(len(gc.get_objects()), 9087db96d56Sopenharmony_ci len(gc.get_objects(generation=None))) 9097db96d56Sopenharmony_ci 9107db96d56Sopenharmony_ci self.assertRaises(ValueError, gc.get_objects, 1000) 9117db96d56Sopenharmony_ci self.assertRaises(ValueError, gc.get_objects, -1000) 9127db96d56Sopenharmony_ci self.assertRaises(TypeError, gc.get_objects, "1") 9137db96d56Sopenharmony_ci self.assertRaises(TypeError, gc.get_objects, 1.234) 9147db96d56Sopenharmony_ci 9157db96d56Sopenharmony_ci def test_resurrection_only_happens_once_per_object(self): 9167db96d56Sopenharmony_ci class A: # simple self-loop 9177db96d56Sopenharmony_ci def __init__(self): 9187db96d56Sopenharmony_ci self.me = self 9197db96d56Sopenharmony_ci 9207db96d56Sopenharmony_ci class Lazarus(A): 9217db96d56Sopenharmony_ci resurrected = 0 9227db96d56Sopenharmony_ci resurrected_instances = [] 9237db96d56Sopenharmony_ci 9247db96d56Sopenharmony_ci def __del__(self): 9257db96d56Sopenharmony_ci Lazarus.resurrected += 1 9267db96d56Sopenharmony_ci Lazarus.resurrected_instances.append(self) 9277db96d56Sopenharmony_ci 9287db96d56Sopenharmony_ci gc.collect() 9297db96d56Sopenharmony_ci gc.disable() 9307db96d56Sopenharmony_ci 9317db96d56Sopenharmony_ci # We start with 0 resurrections 9327db96d56Sopenharmony_ci laz = Lazarus() 9337db96d56Sopenharmony_ci self.assertEqual(Lazarus.resurrected, 0) 9347db96d56Sopenharmony_ci 9357db96d56Sopenharmony_ci # Deleting the instance and triggering a collection 9367db96d56Sopenharmony_ci # resurrects the object 9377db96d56Sopenharmony_ci del laz 9387db96d56Sopenharmony_ci gc.collect() 9397db96d56Sopenharmony_ci self.assertEqual(Lazarus.resurrected, 1) 9407db96d56Sopenharmony_ci self.assertEqual(len(Lazarus.resurrected_instances), 1) 9417db96d56Sopenharmony_ci 9427db96d56Sopenharmony_ci # Clearing the references and forcing a collection 9437db96d56Sopenharmony_ci # should not resurrect the object again. 9447db96d56Sopenharmony_ci Lazarus.resurrected_instances.clear() 9457db96d56Sopenharmony_ci self.assertEqual(Lazarus.resurrected, 1) 9467db96d56Sopenharmony_ci gc.collect() 9477db96d56Sopenharmony_ci self.assertEqual(Lazarus.resurrected, 1) 9487db96d56Sopenharmony_ci 9497db96d56Sopenharmony_ci gc.enable() 9507db96d56Sopenharmony_ci 9517db96d56Sopenharmony_ci def test_resurrection_is_transitive(self): 9527db96d56Sopenharmony_ci class Cargo: 9537db96d56Sopenharmony_ci def __init__(self): 9547db96d56Sopenharmony_ci self.me = self 9557db96d56Sopenharmony_ci 9567db96d56Sopenharmony_ci class Lazarus: 9577db96d56Sopenharmony_ci resurrected_instances = [] 9587db96d56Sopenharmony_ci 9597db96d56Sopenharmony_ci def __del__(self): 9607db96d56Sopenharmony_ci Lazarus.resurrected_instances.append(self) 9617db96d56Sopenharmony_ci 9627db96d56Sopenharmony_ci gc.collect() 9637db96d56Sopenharmony_ci gc.disable() 9647db96d56Sopenharmony_ci 9657db96d56Sopenharmony_ci laz = Lazarus() 9667db96d56Sopenharmony_ci cargo = Cargo() 9677db96d56Sopenharmony_ci cargo_id = id(cargo) 9687db96d56Sopenharmony_ci 9697db96d56Sopenharmony_ci # Create a cycle between cargo and laz 9707db96d56Sopenharmony_ci laz.cargo = cargo 9717db96d56Sopenharmony_ci cargo.laz = laz 9727db96d56Sopenharmony_ci 9737db96d56Sopenharmony_ci # Drop the references, force a collection and check that 9747db96d56Sopenharmony_ci # everything was resurrected. 9757db96d56Sopenharmony_ci del laz, cargo 9767db96d56Sopenharmony_ci gc.collect() 9777db96d56Sopenharmony_ci self.assertEqual(len(Lazarus.resurrected_instances), 1) 9787db96d56Sopenharmony_ci instance = Lazarus.resurrected_instances.pop() 9797db96d56Sopenharmony_ci self.assertTrue(hasattr(instance, "cargo")) 9807db96d56Sopenharmony_ci self.assertEqual(id(instance.cargo), cargo_id) 9817db96d56Sopenharmony_ci 9827db96d56Sopenharmony_ci gc.collect() 9837db96d56Sopenharmony_ci gc.enable() 9847db96d56Sopenharmony_ci 9857db96d56Sopenharmony_ci def test_resurrection_does_not_block_cleanup_of_other_objects(self): 9867db96d56Sopenharmony_ci 9877db96d56Sopenharmony_ci # When a finalizer resurrects objects, stats were reporting them as 9887db96d56Sopenharmony_ci # having been collected. This affected both collect()'s return 9897db96d56Sopenharmony_ci # value and the dicts returned by get_stats(). 9907db96d56Sopenharmony_ci N = 100 9917db96d56Sopenharmony_ci 9927db96d56Sopenharmony_ci class A: # simple self-loop 9937db96d56Sopenharmony_ci def __init__(self): 9947db96d56Sopenharmony_ci self.me = self 9957db96d56Sopenharmony_ci 9967db96d56Sopenharmony_ci class Z(A): # resurrecting __del__ 9977db96d56Sopenharmony_ci def __del__(self): 9987db96d56Sopenharmony_ci zs.append(self) 9997db96d56Sopenharmony_ci 10007db96d56Sopenharmony_ci zs = [] 10017db96d56Sopenharmony_ci 10027db96d56Sopenharmony_ci def getstats(): 10037db96d56Sopenharmony_ci d = gc.get_stats()[-1] 10047db96d56Sopenharmony_ci return d['collected'], d['uncollectable'] 10057db96d56Sopenharmony_ci 10067db96d56Sopenharmony_ci gc.collect() 10077db96d56Sopenharmony_ci gc.disable() 10087db96d56Sopenharmony_ci 10097db96d56Sopenharmony_ci # No problems if just collecting A() instances. 10107db96d56Sopenharmony_ci oldc, oldnc = getstats() 10117db96d56Sopenharmony_ci for i in range(N): 10127db96d56Sopenharmony_ci A() 10137db96d56Sopenharmony_ci t = gc.collect() 10147db96d56Sopenharmony_ci c, nc = getstats() 10157db96d56Sopenharmony_ci self.assertEqual(t, N) # instance objects 10167db96d56Sopenharmony_ci self.assertEqual(c - oldc, N) 10177db96d56Sopenharmony_ci self.assertEqual(nc - oldnc, 0) 10187db96d56Sopenharmony_ci 10197db96d56Sopenharmony_ci # But Z() is not actually collected. 10207db96d56Sopenharmony_ci oldc, oldnc = c, nc 10217db96d56Sopenharmony_ci Z() 10227db96d56Sopenharmony_ci # Nothing is collected - Z() is merely resurrected. 10237db96d56Sopenharmony_ci t = gc.collect() 10247db96d56Sopenharmony_ci c, nc = getstats() 10257db96d56Sopenharmony_ci self.assertEqual(t, 0) 10267db96d56Sopenharmony_ci self.assertEqual(c - oldc, 0) 10277db96d56Sopenharmony_ci self.assertEqual(nc - oldnc, 0) 10287db96d56Sopenharmony_ci 10297db96d56Sopenharmony_ci # Z() should not prevent anything else from being collected. 10307db96d56Sopenharmony_ci oldc, oldnc = c, nc 10317db96d56Sopenharmony_ci for i in range(N): 10327db96d56Sopenharmony_ci A() 10337db96d56Sopenharmony_ci Z() 10347db96d56Sopenharmony_ci t = gc.collect() 10357db96d56Sopenharmony_ci c, nc = getstats() 10367db96d56Sopenharmony_ci self.assertEqual(t, N) 10377db96d56Sopenharmony_ci self.assertEqual(c - oldc, N) 10387db96d56Sopenharmony_ci self.assertEqual(nc - oldnc, 0) 10397db96d56Sopenharmony_ci 10407db96d56Sopenharmony_ci # The A() trash should have been reclaimed already but the 10417db96d56Sopenharmony_ci # 2 copies of Z are still in zs (and the associated dicts). 10427db96d56Sopenharmony_ci oldc, oldnc = c, nc 10437db96d56Sopenharmony_ci zs.clear() 10447db96d56Sopenharmony_ci t = gc.collect() 10457db96d56Sopenharmony_ci c, nc = getstats() 10467db96d56Sopenharmony_ci self.assertEqual(t, 2) 10477db96d56Sopenharmony_ci self.assertEqual(c - oldc, 2) 10487db96d56Sopenharmony_ci self.assertEqual(nc - oldnc, 0) 10497db96d56Sopenharmony_ci 10507db96d56Sopenharmony_ci gc.enable() 10517db96d56Sopenharmony_ci 10527db96d56Sopenharmony_ci @unittest.skipIf(ContainerNoGC is None, 10537db96d56Sopenharmony_ci 'requires ContainerNoGC extension type') 10547db96d56Sopenharmony_ci def test_trash_weakref_clear(self): 10557db96d56Sopenharmony_ci # Test that trash weakrefs are properly cleared (bpo-38006). 10567db96d56Sopenharmony_ci # 10577db96d56Sopenharmony_ci # Structure we are creating: 10587db96d56Sopenharmony_ci # 10597db96d56Sopenharmony_ci # Z <- Y <- A--+--> WZ -> C 10607db96d56Sopenharmony_ci # ^ | 10617db96d56Sopenharmony_ci # +--+ 10627db96d56Sopenharmony_ci # where: 10637db96d56Sopenharmony_ci # WZ is a weakref to Z with callback C 10647db96d56Sopenharmony_ci # Y doesn't implement tp_traverse 10657db96d56Sopenharmony_ci # A contains a reference to itself, Y and WZ 10667db96d56Sopenharmony_ci # 10677db96d56Sopenharmony_ci # A, Y, Z, WZ are all trash. The GC doesn't know that Z is trash 10687db96d56Sopenharmony_ci # because Y does not implement tp_traverse. To show the bug, WZ needs 10697db96d56Sopenharmony_ci # to live long enough so that Z is deallocated before it. Then, if 10707db96d56Sopenharmony_ci # gcmodule is buggy, when Z is being deallocated, C will run. 10717db96d56Sopenharmony_ci # 10727db96d56Sopenharmony_ci # To ensure WZ lives long enough, we put it in a second reference 10737db96d56Sopenharmony_ci # cycle. That trick only works due to the ordering of the GC prev/next 10747db96d56Sopenharmony_ci # linked lists. So, this test is a bit fragile. 10757db96d56Sopenharmony_ci # 10767db96d56Sopenharmony_ci # The bug reported in bpo-38006 is caused because the GC did not 10777db96d56Sopenharmony_ci # clear WZ before starting the process of calling tp_clear on the 10787db96d56Sopenharmony_ci # trash. Normally, handle_weakrefs() would find the weakref via Z and 10797db96d56Sopenharmony_ci # clear it. However, since the GC cannot find Z, WR is not cleared and 10807db96d56Sopenharmony_ci # it can execute during delete_garbage(). That can lead to disaster 10817db96d56Sopenharmony_ci # since the callback might tinker with objects that have already had 10827db96d56Sopenharmony_ci # tp_clear called on them (leaving them in possibly invalid states). 10837db96d56Sopenharmony_ci 10847db96d56Sopenharmony_ci callback = unittest.mock.Mock() 10857db96d56Sopenharmony_ci 10867db96d56Sopenharmony_ci class A: 10877db96d56Sopenharmony_ci __slots__ = ['a', 'y', 'wz'] 10887db96d56Sopenharmony_ci 10897db96d56Sopenharmony_ci class Z: 10907db96d56Sopenharmony_ci pass 10917db96d56Sopenharmony_ci 10927db96d56Sopenharmony_ci # setup required object graph, as described above 10937db96d56Sopenharmony_ci a = A() 10947db96d56Sopenharmony_ci a.a = a 10957db96d56Sopenharmony_ci a.y = ContainerNoGC(Z()) 10967db96d56Sopenharmony_ci a.wz = weakref.ref(a.y.value, callback) 10977db96d56Sopenharmony_ci # create second cycle to keep WZ alive longer 10987db96d56Sopenharmony_ci wr_cycle = [a.wz] 10997db96d56Sopenharmony_ci wr_cycle.append(wr_cycle) 11007db96d56Sopenharmony_ci # ensure trash unrelated to this test is gone 11017db96d56Sopenharmony_ci gc.collect() 11027db96d56Sopenharmony_ci gc.disable() 11037db96d56Sopenharmony_ci # release references and create trash 11047db96d56Sopenharmony_ci del a, wr_cycle 11057db96d56Sopenharmony_ci gc.collect() 11067db96d56Sopenharmony_ci # if called, it means there is a bug in the GC. The weakref should be 11077db96d56Sopenharmony_ci # cleared before Z dies. 11087db96d56Sopenharmony_ci callback.assert_not_called() 11097db96d56Sopenharmony_ci gc.enable() 11107db96d56Sopenharmony_ci 11117db96d56Sopenharmony_ci 11127db96d56Sopenharmony_ciclass GCCallbackTests(unittest.TestCase): 11137db96d56Sopenharmony_ci def setUp(self): 11147db96d56Sopenharmony_ci # Save gc state and disable it. 11157db96d56Sopenharmony_ci self.enabled = gc.isenabled() 11167db96d56Sopenharmony_ci gc.disable() 11177db96d56Sopenharmony_ci self.debug = gc.get_debug() 11187db96d56Sopenharmony_ci gc.set_debug(0) 11197db96d56Sopenharmony_ci gc.callbacks.append(self.cb1) 11207db96d56Sopenharmony_ci gc.callbacks.append(self.cb2) 11217db96d56Sopenharmony_ci self.othergarbage = [] 11227db96d56Sopenharmony_ci 11237db96d56Sopenharmony_ci def tearDown(self): 11247db96d56Sopenharmony_ci # Restore gc state 11257db96d56Sopenharmony_ci del self.visit 11267db96d56Sopenharmony_ci gc.callbacks.remove(self.cb1) 11277db96d56Sopenharmony_ci gc.callbacks.remove(self.cb2) 11287db96d56Sopenharmony_ci gc.set_debug(self.debug) 11297db96d56Sopenharmony_ci if self.enabled: 11307db96d56Sopenharmony_ci gc.enable() 11317db96d56Sopenharmony_ci # destroy any uncollectables 11327db96d56Sopenharmony_ci gc.collect() 11337db96d56Sopenharmony_ci for obj in gc.garbage: 11347db96d56Sopenharmony_ci if isinstance(obj, Uncollectable): 11357db96d56Sopenharmony_ci obj.partner = None 11367db96d56Sopenharmony_ci del gc.garbage[:] 11377db96d56Sopenharmony_ci del self.othergarbage 11387db96d56Sopenharmony_ci gc.collect() 11397db96d56Sopenharmony_ci 11407db96d56Sopenharmony_ci def preclean(self): 11417db96d56Sopenharmony_ci # Remove all fluff from the system. Invoke this function 11427db96d56Sopenharmony_ci # manually rather than through self.setUp() for maximum 11437db96d56Sopenharmony_ci # safety. 11447db96d56Sopenharmony_ci self.visit = [] 11457db96d56Sopenharmony_ci gc.collect() 11467db96d56Sopenharmony_ci garbage, gc.garbage[:] = gc.garbage[:], [] 11477db96d56Sopenharmony_ci self.othergarbage.append(garbage) 11487db96d56Sopenharmony_ci self.visit = [] 11497db96d56Sopenharmony_ci 11507db96d56Sopenharmony_ci def cb1(self, phase, info): 11517db96d56Sopenharmony_ci self.visit.append((1, phase, dict(info))) 11527db96d56Sopenharmony_ci 11537db96d56Sopenharmony_ci def cb2(self, phase, info): 11547db96d56Sopenharmony_ci self.visit.append((2, phase, dict(info))) 11557db96d56Sopenharmony_ci if phase == "stop" and hasattr(self, "cleanup"): 11567db96d56Sopenharmony_ci # Clean Uncollectable from garbage 11577db96d56Sopenharmony_ci uc = [e for e in gc.garbage if isinstance(e, Uncollectable)] 11587db96d56Sopenharmony_ci gc.garbage[:] = [e for e in gc.garbage 11597db96d56Sopenharmony_ci if not isinstance(e, Uncollectable)] 11607db96d56Sopenharmony_ci for e in uc: 11617db96d56Sopenharmony_ci e.partner = None 11627db96d56Sopenharmony_ci 11637db96d56Sopenharmony_ci def test_collect(self): 11647db96d56Sopenharmony_ci self.preclean() 11657db96d56Sopenharmony_ci gc.collect() 11667db96d56Sopenharmony_ci # Algorithmically verify the contents of self.visit 11677db96d56Sopenharmony_ci # because it is long and tortuous. 11687db96d56Sopenharmony_ci 11697db96d56Sopenharmony_ci # Count the number of visits to each callback 11707db96d56Sopenharmony_ci n = [v[0] for v in self.visit] 11717db96d56Sopenharmony_ci n1 = [i for i in n if i == 1] 11727db96d56Sopenharmony_ci n2 = [i for i in n if i == 2] 11737db96d56Sopenharmony_ci self.assertEqual(n1, [1]*2) 11747db96d56Sopenharmony_ci self.assertEqual(n2, [2]*2) 11757db96d56Sopenharmony_ci 11767db96d56Sopenharmony_ci # Count that we got the right number of start and stop callbacks. 11777db96d56Sopenharmony_ci n = [v[1] for v in self.visit] 11787db96d56Sopenharmony_ci n1 = [i for i in n if i == "start"] 11797db96d56Sopenharmony_ci n2 = [i for i in n if i == "stop"] 11807db96d56Sopenharmony_ci self.assertEqual(n1, ["start"]*2) 11817db96d56Sopenharmony_ci self.assertEqual(n2, ["stop"]*2) 11827db96d56Sopenharmony_ci 11837db96d56Sopenharmony_ci # Check that we got the right info dict for all callbacks 11847db96d56Sopenharmony_ci for v in self.visit: 11857db96d56Sopenharmony_ci info = v[2] 11867db96d56Sopenharmony_ci self.assertTrue("generation" in info) 11877db96d56Sopenharmony_ci self.assertTrue("collected" in info) 11887db96d56Sopenharmony_ci self.assertTrue("uncollectable" in info) 11897db96d56Sopenharmony_ci 11907db96d56Sopenharmony_ci def test_collect_generation(self): 11917db96d56Sopenharmony_ci self.preclean() 11927db96d56Sopenharmony_ci gc.collect(2) 11937db96d56Sopenharmony_ci for v in self.visit: 11947db96d56Sopenharmony_ci info = v[2] 11957db96d56Sopenharmony_ci self.assertEqual(info["generation"], 2) 11967db96d56Sopenharmony_ci 11977db96d56Sopenharmony_ci @cpython_only 11987db96d56Sopenharmony_ci def test_collect_garbage(self): 11997db96d56Sopenharmony_ci self.preclean() 12007db96d56Sopenharmony_ci # Each of these cause two objects to be garbage: 12017db96d56Sopenharmony_ci Uncollectable() 12027db96d56Sopenharmony_ci Uncollectable() 12037db96d56Sopenharmony_ci C1055820(666) 12047db96d56Sopenharmony_ci gc.collect() 12057db96d56Sopenharmony_ci for v in self.visit: 12067db96d56Sopenharmony_ci if v[1] != "stop": 12077db96d56Sopenharmony_ci continue 12087db96d56Sopenharmony_ci info = v[2] 12097db96d56Sopenharmony_ci self.assertEqual(info["collected"], 1) 12107db96d56Sopenharmony_ci self.assertEqual(info["uncollectable"], 4) 12117db96d56Sopenharmony_ci 12127db96d56Sopenharmony_ci # We should now have the Uncollectables in gc.garbage 12137db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), 4) 12147db96d56Sopenharmony_ci for e in gc.garbage: 12157db96d56Sopenharmony_ci self.assertIsInstance(e, Uncollectable) 12167db96d56Sopenharmony_ci 12177db96d56Sopenharmony_ci # Now, let our callback handle the Uncollectable instances 12187db96d56Sopenharmony_ci self.cleanup=True 12197db96d56Sopenharmony_ci self.visit = [] 12207db96d56Sopenharmony_ci gc.garbage[:] = [] 12217db96d56Sopenharmony_ci gc.collect() 12227db96d56Sopenharmony_ci for v in self.visit: 12237db96d56Sopenharmony_ci if v[1] != "stop": 12247db96d56Sopenharmony_ci continue 12257db96d56Sopenharmony_ci info = v[2] 12267db96d56Sopenharmony_ci self.assertEqual(info["collected"], 0) 12277db96d56Sopenharmony_ci self.assertEqual(info["uncollectable"], 2) 12287db96d56Sopenharmony_ci 12297db96d56Sopenharmony_ci # Uncollectables should be gone 12307db96d56Sopenharmony_ci self.assertEqual(len(gc.garbage), 0) 12317db96d56Sopenharmony_ci 12327db96d56Sopenharmony_ci 12337db96d56Sopenharmony_ci @unittest.skipIf(BUILD_WITH_NDEBUG, 12347db96d56Sopenharmony_ci 'built with -NDEBUG') 12357db96d56Sopenharmony_ci def test_refcount_errors(self): 12367db96d56Sopenharmony_ci self.preclean() 12377db96d56Sopenharmony_ci # Verify the "handling" of objects with broken refcounts 12387db96d56Sopenharmony_ci 12397db96d56Sopenharmony_ci # Skip the test if ctypes is not available 12407db96d56Sopenharmony_ci import_module("ctypes") 12417db96d56Sopenharmony_ci 12427db96d56Sopenharmony_ci import subprocess 12437db96d56Sopenharmony_ci code = textwrap.dedent(''' 12447db96d56Sopenharmony_ci from test.support import gc_collect, SuppressCrashReport 12457db96d56Sopenharmony_ci 12467db96d56Sopenharmony_ci a = [1, 2, 3] 12477db96d56Sopenharmony_ci b = [a] 12487db96d56Sopenharmony_ci 12497db96d56Sopenharmony_ci # Avoid coredump when Py_FatalError() calls abort() 12507db96d56Sopenharmony_ci SuppressCrashReport().__enter__() 12517db96d56Sopenharmony_ci 12527db96d56Sopenharmony_ci # Simulate the refcount of "a" being too low (compared to the 12537db96d56Sopenharmony_ci # references held on it by live data), but keeping it above zero 12547db96d56Sopenharmony_ci # (to avoid deallocating it): 12557db96d56Sopenharmony_ci import ctypes 12567db96d56Sopenharmony_ci ctypes.pythonapi.Py_DecRef(ctypes.py_object(a)) 12577db96d56Sopenharmony_ci 12587db96d56Sopenharmony_ci # The garbage collector should now have a fatal error 12597db96d56Sopenharmony_ci # when it reaches the broken object 12607db96d56Sopenharmony_ci gc_collect() 12617db96d56Sopenharmony_ci ''') 12627db96d56Sopenharmony_ci p = subprocess.Popen([sys.executable, "-c", code], 12637db96d56Sopenharmony_ci stdout=subprocess.PIPE, 12647db96d56Sopenharmony_ci stderr=subprocess.PIPE) 12657db96d56Sopenharmony_ci stdout, stderr = p.communicate() 12667db96d56Sopenharmony_ci p.stdout.close() 12677db96d56Sopenharmony_ci p.stderr.close() 12687db96d56Sopenharmony_ci # Verify that stderr has a useful error message: 12697db96d56Sopenharmony_ci self.assertRegex(stderr, 12707db96d56Sopenharmony_ci br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.') 12717db96d56Sopenharmony_ci self.assertRegex(stderr, 12727db96d56Sopenharmony_ci br'refcount is too small') 12737db96d56Sopenharmony_ci # "address : 0x7fb5062efc18" 12747db96d56Sopenharmony_ci # "address : 7FB5062EFC18" 12757db96d56Sopenharmony_ci address_regex = br'[0-9a-fA-Fx]+' 12767db96d56Sopenharmony_ci self.assertRegex(stderr, 12777db96d56Sopenharmony_ci br'object address : ' + address_regex) 12787db96d56Sopenharmony_ci self.assertRegex(stderr, 12797db96d56Sopenharmony_ci br'object refcount : 1') 12807db96d56Sopenharmony_ci self.assertRegex(stderr, 12817db96d56Sopenharmony_ci br'object type : ' + address_regex) 12827db96d56Sopenharmony_ci self.assertRegex(stderr, 12837db96d56Sopenharmony_ci br'object type name: list') 12847db96d56Sopenharmony_ci self.assertRegex(stderr, 12857db96d56Sopenharmony_ci br'object repr : \[1, 2, 3\]') 12867db96d56Sopenharmony_ci 12877db96d56Sopenharmony_ci 12887db96d56Sopenharmony_ciclass GCTogglingTests(unittest.TestCase): 12897db96d56Sopenharmony_ci def setUp(self): 12907db96d56Sopenharmony_ci gc.enable() 12917db96d56Sopenharmony_ci 12927db96d56Sopenharmony_ci def tearDown(self): 12937db96d56Sopenharmony_ci gc.disable() 12947db96d56Sopenharmony_ci 12957db96d56Sopenharmony_ci def test_bug1055820c(self): 12967db96d56Sopenharmony_ci # Corresponds to temp2c.py in the bug report. This is pretty 12977db96d56Sopenharmony_ci # elaborate. 12987db96d56Sopenharmony_ci 12997db96d56Sopenharmony_ci c0 = C1055820(0) 13007db96d56Sopenharmony_ci # Move c0 into generation 2. 13017db96d56Sopenharmony_ci gc.collect() 13027db96d56Sopenharmony_ci 13037db96d56Sopenharmony_ci c1 = C1055820(1) 13047db96d56Sopenharmony_ci c1.keep_c0_alive = c0 13057db96d56Sopenharmony_ci del c0.loop # now only c1 keeps c0 alive 13067db96d56Sopenharmony_ci 13077db96d56Sopenharmony_ci c2 = C1055820(2) 13087db96d56Sopenharmony_ci c2wr = weakref.ref(c2) # no callback! 13097db96d56Sopenharmony_ci 13107db96d56Sopenharmony_ci ouch = [] 13117db96d56Sopenharmony_ci def callback(ignored): 13127db96d56Sopenharmony_ci ouch[:] = [c2wr()] 13137db96d56Sopenharmony_ci 13147db96d56Sopenharmony_ci # The callback gets associated with a wr on an object in generation 2. 13157db96d56Sopenharmony_ci c0wr = weakref.ref(c0, callback) 13167db96d56Sopenharmony_ci 13177db96d56Sopenharmony_ci c0 = c1 = c2 = None 13187db96d56Sopenharmony_ci 13197db96d56Sopenharmony_ci # What we've set up: c0, c1, and c2 are all trash now. c0 is in 13207db96d56Sopenharmony_ci # generation 2. The only thing keeping it alive is that c1 points to 13217db96d56Sopenharmony_ci # it. c1 and c2 are in generation 0, and are in self-loops. There's a 13227db96d56Sopenharmony_ci # global weakref to c2 (c2wr), but that weakref has no callback. 13237db96d56Sopenharmony_ci # There's also a global weakref to c0 (c0wr), and that does have a 13247db96d56Sopenharmony_ci # callback, and that callback references c2 via c2wr(). 13257db96d56Sopenharmony_ci # 13267db96d56Sopenharmony_ci # c0 has a wr with callback, which references c2wr 13277db96d56Sopenharmony_ci # ^ 13287db96d56Sopenharmony_ci # | 13297db96d56Sopenharmony_ci # | Generation 2 above dots 13307db96d56Sopenharmony_ci #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . 13317db96d56Sopenharmony_ci # | Generation 0 below dots 13327db96d56Sopenharmony_ci # | 13337db96d56Sopenharmony_ci # | 13347db96d56Sopenharmony_ci # ^->c1 ^->c2 has a wr but no callback 13357db96d56Sopenharmony_ci # | | | | 13367db96d56Sopenharmony_ci # <--v <--v 13377db96d56Sopenharmony_ci # 13387db96d56Sopenharmony_ci # So this is the nightmare: when generation 0 gets collected, we see 13397db96d56Sopenharmony_ci # that c2 has a callback-free weakref, and c1 doesn't even have a 13407db96d56Sopenharmony_ci # weakref. Collecting generation 0 doesn't see c0 at all, and c0 is 13417db96d56Sopenharmony_ci # the only object that has a weakref with a callback. gc clears c1 13427db96d56Sopenharmony_ci # and c2. Clearing c1 has the side effect of dropping the refcount on 13437db96d56Sopenharmony_ci # c0 to 0, so c0 goes away (despite that it's in an older generation) 13447db96d56Sopenharmony_ci # and c0's wr callback triggers. That in turn materializes a reference 13457db96d56Sopenharmony_ci # to c2 via c2wr(), but c2 gets cleared anyway by gc. 13467db96d56Sopenharmony_ci 13477db96d56Sopenharmony_ci # We want to let gc happen "naturally", to preserve the distinction 13487db96d56Sopenharmony_ci # between generations. 13497db96d56Sopenharmony_ci junk = [] 13507db96d56Sopenharmony_ci i = 0 13517db96d56Sopenharmony_ci detector = GC_Detector() 13527db96d56Sopenharmony_ci while not detector.gc_happened: 13537db96d56Sopenharmony_ci i += 1 13547db96d56Sopenharmony_ci if i > 10000: 13557db96d56Sopenharmony_ci self.fail("gc didn't happen after 10000 iterations") 13567db96d56Sopenharmony_ci self.assertEqual(len(ouch), 0) 13577db96d56Sopenharmony_ci junk.append([]) # this will eventually trigger gc 13587db96d56Sopenharmony_ci 13597db96d56Sopenharmony_ci self.assertEqual(len(ouch), 1) # else the callback wasn't invoked 13607db96d56Sopenharmony_ci for x in ouch: 13617db96d56Sopenharmony_ci # If the callback resurrected c2, the instance would be damaged, 13627db96d56Sopenharmony_ci # with an empty __dict__. 13637db96d56Sopenharmony_ci self.assertEqual(x, None) 13647db96d56Sopenharmony_ci 13657db96d56Sopenharmony_ci def test_bug1055820d(self): 13667db96d56Sopenharmony_ci # Corresponds to temp2d.py in the bug report. This is very much like 13677db96d56Sopenharmony_ci # test_bug1055820c, but uses a __del__ method instead of a weakref 13687db96d56Sopenharmony_ci # callback to sneak in a resurrection of cyclic trash. 13697db96d56Sopenharmony_ci 13707db96d56Sopenharmony_ci ouch = [] 13717db96d56Sopenharmony_ci class D(C1055820): 13727db96d56Sopenharmony_ci def __del__(self): 13737db96d56Sopenharmony_ci ouch[:] = [c2wr()] 13747db96d56Sopenharmony_ci 13757db96d56Sopenharmony_ci d0 = D(0) 13767db96d56Sopenharmony_ci # Move all the above into generation 2. 13777db96d56Sopenharmony_ci gc.collect() 13787db96d56Sopenharmony_ci 13797db96d56Sopenharmony_ci c1 = C1055820(1) 13807db96d56Sopenharmony_ci c1.keep_d0_alive = d0 13817db96d56Sopenharmony_ci del d0.loop # now only c1 keeps d0 alive 13827db96d56Sopenharmony_ci 13837db96d56Sopenharmony_ci c2 = C1055820(2) 13847db96d56Sopenharmony_ci c2wr = weakref.ref(c2) # no callback! 13857db96d56Sopenharmony_ci 13867db96d56Sopenharmony_ci d0 = c1 = c2 = None 13877db96d56Sopenharmony_ci 13887db96d56Sopenharmony_ci # What we've set up: d0, c1, and c2 are all trash now. d0 is in 13897db96d56Sopenharmony_ci # generation 2. The only thing keeping it alive is that c1 points to 13907db96d56Sopenharmony_ci # it. c1 and c2 are in generation 0, and are in self-loops. There's 13917db96d56Sopenharmony_ci # a global weakref to c2 (c2wr), but that weakref has no callback. 13927db96d56Sopenharmony_ci # There are no other weakrefs. 13937db96d56Sopenharmony_ci # 13947db96d56Sopenharmony_ci # d0 has a __del__ method that references c2wr 13957db96d56Sopenharmony_ci # ^ 13967db96d56Sopenharmony_ci # | 13977db96d56Sopenharmony_ci # | Generation 2 above dots 13987db96d56Sopenharmony_ci #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . 13997db96d56Sopenharmony_ci # | Generation 0 below dots 14007db96d56Sopenharmony_ci # | 14017db96d56Sopenharmony_ci # | 14027db96d56Sopenharmony_ci # ^->c1 ^->c2 has a wr but no callback 14037db96d56Sopenharmony_ci # | | | | 14047db96d56Sopenharmony_ci # <--v <--v 14057db96d56Sopenharmony_ci # 14067db96d56Sopenharmony_ci # So this is the nightmare: when generation 0 gets collected, we see 14077db96d56Sopenharmony_ci # that c2 has a callback-free weakref, and c1 doesn't even have a 14087db96d56Sopenharmony_ci # weakref. Collecting generation 0 doesn't see d0 at all. gc clears 14097db96d56Sopenharmony_ci # c1 and c2. Clearing c1 has the side effect of dropping the refcount 14107db96d56Sopenharmony_ci # on d0 to 0, so d0 goes away (despite that it's in an older 14117db96d56Sopenharmony_ci # generation) and d0's __del__ triggers. That in turn materializes 14127db96d56Sopenharmony_ci # a reference to c2 via c2wr(), but c2 gets cleared anyway by gc. 14137db96d56Sopenharmony_ci 14147db96d56Sopenharmony_ci # We want to let gc happen "naturally", to preserve the distinction 14157db96d56Sopenharmony_ci # between generations. 14167db96d56Sopenharmony_ci detector = GC_Detector() 14177db96d56Sopenharmony_ci junk = [] 14187db96d56Sopenharmony_ci i = 0 14197db96d56Sopenharmony_ci while not detector.gc_happened: 14207db96d56Sopenharmony_ci i += 1 14217db96d56Sopenharmony_ci if i > 10000: 14227db96d56Sopenharmony_ci self.fail("gc didn't happen after 10000 iterations") 14237db96d56Sopenharmony_ci self.assertEqual(len(ouch), 0) 14247db96d56Sopenharmony_ci junk.append([]) # this will eventually trigger gc 14257db96d56Sopenharmony_ci 14267db96d56Sopenharmony_ci self.assertEqual(len(ouch), 1) # else __del__ wasn't invoked 14277db96d56Sopenharmony_ci for x in ouch: 14287db96d56Sopenharmony_ci # If __del__ resurrected c2, the instance would be damaged, with an 14297db96d56Sopenharmony_ci # empty __dict__. 14307db96d56Sopenharmony_ci self.assertEqual(x, None) 14317db96d56Sopenharmony_ci 14327db96d56Sopenharmony_ci 14337db96d56Sopenharmony_ciclass PythonFinalizationTests(unittest.TestCase): 14347db96d56Sopenharmony_ci def test_ast_fini(self): 14357db96d56Sopenharmony_ci # bpo-44184: Regression test for subtype_dealloc() when deallocating 14367db96d56Sopenharmony_ci # an AST instance also destroy its AST type: subtype_dealloc() must 14377db96d56Sopenharmony_ci # not access the type memory after deallocating the instance, since 14387db96d56Sopenharmony_ci # the type memory can be freed as well. The test is also related to 14397db96d56Sopenharmony_ci # _PyAST_Fini() which clears references to AST types. 14407db96d56Sopenharmony_ci code = textwrap.dedent(""" 14417db96d56Sopenharmony_ci import ast 14427db96d56Sopenharmony_ci import codecs 14437db96d56Sopenharmony_ci 14447db96d56Sopenharmony_ci # Small AST tree to keep their AST types alive 14457db96d56Sopenharmony_ci tree = ast.parse("def f(x, y): return 2*x-y") 14467db96d56Sopenharmony_ci x = [tree] 14477db96d56Sopenharmony_ci x.append(x) 14487db96d56Sopenharmony_ci 14497db96d56Sopenharmony_ci # Put the cycle somewhere to survive until the last GC collection. 14507db96d56Sopenharmony_ci # Codec search functions are only cleared at the end of 14517db96d56Sopenharmony_ci # interpreter_clear(). 14527db96d56Sopenharmony_ci def search_func(encoding): 14537db96d56Sopenharmony_ci return None 14547db96d56Sopenharmony_ci search_func.a = x 14557db96d56Sopenharmony_ci codecs.register(search_func) 14567db96d56Sopenharmony_ci """) 14577db96d56Sopenharmony_ci assert_python_ok("-c", code) 14587db96d56Sopenharmony_ci 14597db96d56Sopenharmony_ci 14607db96d56Sopenharmony_cidef setUpModule(): 14617db96d56Sopenharmony_ci global enabled, debug 14627db96d56Sopenharmony_ci enabled = gc.isenabled() 14637db96d56Sopenharmony_ci gc.disable() 14647db96d56Sopenharmony_ci assert not gc.isenabled() 14657db96d56Sopenharmony_ci debug = gc.get_debug() 14667db96d56Sopenharmony_ci gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak 14677db96d56Sopenharmony_ci gc.collect() # Delete 2nd generation garbage 14687db96d56Sopenharmony_ci 14697db96d56Sopenharmony_ci 14707db96d56Sopenharmony_cidef tearDownModule(): 14717db96d56Sopenharmony_ci gc.set_debug(debug) 14727db96d56Sopenharmony_ci # test gc.enable() even if GC is disabled by default 14737db96d56Sopenharmony_ci if verbose: 14747db96d56Sopenharmony_ci print("restoring automatic collection") 14757db96d56Sopenharmony_ci # make sure to always test gc.enable() 14767db96d56Sopenharmony_ci gc.enable() 14777db96d56Sopenharmony_ci assert gc.isenabled() 14787db96d56Sopenharmony_ci if not enabled: 14797db96d56Sopenharmony_ci gc.disable() 14807db96d56Sopenharmony_ci 14817db96d56Sopenharmony_ci 14827db96d56Sopenharmony_ciif __name__ == "__main__": 14837db96d56Sopenharmony_ci unittest.main() 1484