| """Regression tests for weakref races in subtype_getweakref() (gh-149816).""" | |
| import gc | |
| import sys | |
| import unittest | |
| import weakref | |
| from test.support import threading_helper | |
| @threading_helper.requires_working_threading() | |
| class TestWeakrefRaces(unittest.TestCase): | |
| ITERATIONS = 5_000 | |
| def test_getweakref_no_crash(self): | |
| """obj.__weakref__ racing with concurrent weakref creation/destruction.""" | |
| class Target: | |
| pass | |
| obj = Target() | |
| def reader(): | |
| for _ in range(self.ITERATIONS): | |
| ref = obj.__weakref__ | |
| self.assertIn(type(ref), (type(None), weakref.ref)) | |
| if ref is not None: | |
| self.assertIn(ref(), (None, obj)) | |
| def mutator(): | |
| for _ in range(self.ITERATIONS): | |
| weakref.ref(obj, lambda _: None) | |
| threading_helper.run_concurrently([reader, mutator]) | |
| def test_getweakref_return_type(self): | |
| """__weakref__ must return None or a live weakref.ref.""" | |
| class Target: | |
| pass | |
| obj = Target() | |
| self.assertIsNone(obj.__weakref__) | |
| ref = weakref.ref(obj) | |
| result = obj.__weakref__ | |
| self.assertIsInstance(result, weakref.ref) | |
| self.assertIs(result(), obj) | |
| del ref, result | |
| gc.collect() | |
| self.assertIsNone(obj.__weakref__) | |
| def test_getweakref_no_refcount_leak(self): | |
| """Each __weakref__ access must not inflate the weakref's refcount.""" | |
| class Target: | |
| pass | |
| obj = Target() | |
| ref = weakref.ref(obj) | |
| before = sys.getrefcount(ref) | |
| for _ in range(1_000): | |
| r = obj.__weakref__ | |
| del r | |
| gc.collect() | |
| self.assertEqual(sys.getrefcount(ref), before) | |
| def test_getweakref_many_readers(self): | |
| """Multiple concurrent readers and mutators must not crash.""" | |
| class Target: | |
| pass | |
| obj = Target() | |
| n = 4 | |
| iters = self.ITERATIONS // n | |
| def reader(): | |
| for _ in range(iters): | |
| ref = obj.__weakref__ | |
| self.assertIn(type(ref), (type(None), weakref.ref)) | |
| def mutator(): | |
| for _ in range(iters): | |
| weakref.ref(obj, lambda _: None) | |
| threading_helper.run_concurrently([reader] * n + [mutator] * n) | |
| if __name__ == "__main__": | |
| unittest.main() |