diff options
author | Neil Schemenauer <nas-github@arctrix.com> | 2019-10-16 03:56:48 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-16 03:56:48 (GMT) |
commit | 392a13bb9346331b087bcd8bb1b37072c126abee (patch) | |
tree | 60356247757d7899b5dda2cec7b85db412dcbf96 /Lib/test | |
parent | fab4ef2df0857ab0c97f3058ac5ec3280c4eb891 (diff) | |
download | cpython-392a13bb9346331b087bcd8bb1b37072c126abee.zip cpython-392a13bb9346331b087bcd8bb1b37072c126abee.tar.gz cpython-392a13bb9346331b087bcd8bb1b37072c126abee.tar.bz2 |
bpo-38006: Add unit test for weakref clear bug (GH-16788)
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_gc.py | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index fe9d6c2..fdb8752 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1,4 +1,5 @@ import unittest +import unittest.mock from test.support import (verbose, refcount_test, run_unittest, strip_python_stderr, cpython_only, start_threads, temp_dir, requires_type_collecting, TESTFN, unlink, @@ -22,6 +23,11 @@ except ImportError: raise TypeError('requires _testcapi.with_tp_del') return C +try: + from _testcapi import ContainerNoGC +except ImportError: + ContainerNoGC = None + ### Support code ############################################################################### @@ -959,6 +965,66 @@ class GCTests(unittest.TestCase): gc.enable() + @unittest.skipIf(ContainerNoGC is None, + 'requires ContainerNoGC extension type') + def test_trash_weakref_clear(self): + # Test that trash weakrefs are properly cleared (bpo-38006). + # + # Structure we are creating: + # + # Z <- Y <- A--+--> WZ -> C + # ^ | + # +--+ + # where: + # WZ is a weakref to Z with callback C + # Y doesn't implement tp_traverse + # A contains a reference to itself, Y and WZ + # + # A, Y, Z, WZ are all trash. The GC doesn't know that Z is trash + # because Y does not implement tp_traverse. To show the bug, WZ needs + # to live long enough so that Z is deallocated before it. Then, if + # gcmodule is buggy, when Z is being deallocated, C will run. + # + # To ensure WZ lives long enough, we put it in a second reference + # cycle. That trick only works due to the ordering of the GC prev/next + # linked lists. So, this test is a bit fragile. + # + # The bug reported in bpo-38006 is caused because the GC did not + # clear WZ before starting the process of calling tp_clear on the + # trash. Normally, handle_weakrefs() would find the weakref via Z and + # clear it. However, since the GC cannot find Z, WR is not cleared and + # it can execute during delete_garbage(). That can lead to disaster + # since the callback might tinker with objects that have already had + # tp_clear called on them (leaving them in possibly invalid states). + + callback = unittest.mock.Mock() + + class A: + __slots__ = ['a', 'y', 'wz'] + + class Z: + pass + + # setup required object graph, as described above + a = A() + a.a = a + a.y = ContainerNoGC(Z()) + a.wz = weakref.ref(a.y.value, callback) + # create second cycle to keep WZ alive longer + wr_cycle = [a.wz] + wr_cycle.append(wr_cycle) + # ensure trash unrelated to this test is gone + gc.collect() + gc.disable() + # release references and create trash + del a, wr_cycle + gc.collect() + # if called, it means there is a bug in the GC. The weakref should be + # cleared before Z dies. + callback.assert_not_called() + gc.enable() + + class GCCallbackTests(unittest.TestCase): def setUp(self): # Save gc state and disable it. |