(self)
| 1528 | @gc_threshold(1000, 0, 0) |
| 1529 | @unittest.skipIf(Py_GIL_DISABLED, "requires GC generations or increments") |
| 1530 | def test_bug1055820d(self): |
| 1531 | # Corresponds to temp2d.py in the bug report. This is very much like |
| 1532 | # test_bug1055820c, but uses a __del__ method instead of a weakref |
| 1533 | # callback to sneak in a resurrection of cyclic trash. |
| 1534 | |
| 1535 | ouch = [] |
| 1536 | class D(C1055820): |
| 1537 | def __del__(self): |
| 1538 | ouch[:] = [c2wr()] |
| 1539 | |
| 1540 | d0 = D(0) |
| 1541 | # Move all the above into generation 2. |
| 1542 | gc.collect() |
| 1543 | |
| 1544 | c1 = C1055820(1) |
| 1545 | c1.keep_d0_alive = d0 |
| 1546 | del d0.loop # now only c1 keeps d0 alive |
| 1547 | |
| 1548 | c2 = C1055820(2) |
| 1549 | c2wr = weakref.ref(c2) # no callback! |
| 1550 | |
| 1551 | d0 = c1 = c2 = None |
| 1552 | |
| 1553 | # What we've set up: d0, c1, and c2 are all trash now. d0 is in |
| 1554 | # generation 2. The only thing keeping it alive is that c1 points to |
| 1555 | # it. c1 and c2 are in generation 0, and are in self-loops. There's |
| 1556 | # a global weakref to c2 (c2wr), but that weakref has no callback. |
| 1557 | # There are no other weakrefs. |
| 1558 | # |
| 1559 | # d0 has a __del__ method that references c2wr |
| 1560 | # ^ |
| 1561 | # | |
| 1562 | # | Generation 2 above dots |
| 1563 | #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . |
| 1564 | # | Generation 0 below dots |
| 1565 | # | |
| 1566 | # | |
| 1567 | # ^->c1 ^->c2 has a wr but no callback |
| 1568 | # | | | | |
| 1569 | # <--v <--v |
| 1570 | # |
| 1571 | # So this is the nightmare: when generation 0 gets collected, we see |
| 1572 | # that c2 has a callback-free weakref, and c1 doesn't even have a |
| 1573 | # weakref. Collecting generation 0 doesn't see d0 at all. gc clears |
| 1574 | # c1 and c2. Clearing c1 has the side effect of dropping the refcount |
| 1575 | # on d0 to 0, so d0 goes away (despite that it's in an older |
| 1576 | # generation) and d0's __del__ triggers. That in turn materializes |
| 1577 | # a reference to c2 via c2wr(), but c2 gets cleared anyway by gc. |
| 1578 | |
| 1579 | # We want to let gc happen "naturally", to preserve the distinction |
| 1580 | # between generations. |
| 1581 | detector = GC_Detector() |
| 1582 | junk = [] |
| 1583 | i = 0 |
| 1584 | if Py_GIL_DISABLED: |
| 1585 | # The free-threaded build doesn't have multiple generations, so |
| 1586 | # just trigger a GC manually. |
| 1587 | gc.collect() |
nothing calls this directly
no test coverage detected