import unittest import threading from threading import Thread from unittest import TestCase import gc from test.support import threading_helper class MyObj: pass @threading_helper.requires_working_threading() class TestGC(TestCase): def test_get_objects(self): event = threading.Event() def gc_thread(): for i in range(100): o = gc.get_objects() event.set() def mutator_thread(): while not event.is_set(): o1 = MyObj() o2 = MyObj() o3 = MyObj() o4 = MyObj() gcs = [Thread(target=gc_thread)] mutators = [Thread(target=mutator_thread) for _ in range(4)] with threading_helper.start_threads(gcs + mutators): pass def test_get_referrers(self): NUM_GC = 2 NUM_MUTATORS = 4 b = threading.Barrier(NUM_GC + NUM_MUTATORS) event = threading.Event() obj = MyObj() def gc_thread(): b.wait() for i in range(100): o = gc.get_referrers(obj) event.set() def mutator_thread(): b.wait() while not event.is_set(): d1 = { "key": obj } d2 = { "key": obj } d3 = { "key": obj } d4 = { "key": obj } gcs = [Thread(target=gc_thread) for _ in range(NUM_GC)] mutators = [Thread(target=mutator_thread) for _ in range(NUM_MUTATORS)] with threading_helper.start_threads(gcs + mutators): pass def test_freeze_object_in_brc_queue(self): # GH-142975: Freezing objects in the BRC queue could result in some # objects having a zero refcount without being deallocated. class Weird: # We need a destructor to trigger the check for object resurrection def __del__(self): pass # This is owned by the main thread, so the subthread will have to increment # this object's reference count. weird = Weird() def evil(): gc.freeze() # Decrement the reference count from this thread, which will trigger the # slow path during resurrection and add our weird object to the BRC queue. nonlocal weird del weird # Collection will merge the object's reference count and make it zero. gc.collect() # Unfreeze the object, making it visible to the GC. gc.unfreeze() gc.collect() thread = Thread(target=evil) thread.start() thread.join() if __name__ == "__main__": unittest.main()