summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2024-10-24 13:33:11 (GMT)
committerGitHub <noreply@github.com>2024-10-24 13:33:11 (GMT)
commite545ead66ce725aae6fb0ad5d733abe806c19750 (patch)
tree00c0125dc1c8929c76ad5d1e1100136cfa6591a9 /Lib
parentb61fece8523d0fa6d9cc6ad3fd855a136c34f0cd (diff)
downloadcpython-e545ead66ce725aae6fb0ad5d733abe806c19750.zip
cpython-e545ead66ce725aae6fb0ad5d733abe806c19750.tar.gz
cpython-e545ead66ce725aae6fb0ad5d733abe806c19750.tar.bz2
gh-125859: Fix crash when `gc.get_objects` is called during GC (#125882)
This fixes a crash when `gc.get_objects()` or `gc.get_referrers()` is called during a GC in the free threading build. Switch to `_PyObjectStack` to avoid corrupting the `struct worklist` linked list maintained by the GC. Also, don't return objects that are frozen (`gc.freeze()`) or in the process of being collected to more closely match the behavior of the default build.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_free_threading/test_gc.py61
-rw-r--r--Lib/test/test_gc.py23
2 files changed, 84 insertions, 0 deletions
diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py
new file mode 100644
index 0000000..401067f
--- /dev/null
+++ b/Lib/test/test_free_threading/test_gc.py
@@ -0,0 +1,61 @@
+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):
+ event = threading.Event()
+
+ obj = MyObj()
+
+ def gc_thread():
+ for i in range(100):
+ o = gc.get_referrers(obj)
+ event.set()
+
+ def mutator_thread():
+ 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(2)]
+ mutators = [Thread(target=mutator_thread) for _ in range(4)]
+ with threading_helper.start_threads(gcs + mutators):
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index bb7df1f..cc2b4fa 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1065,6 +1065,29 @@ class GCTests(unittest.TestCase):
self.assertEqual(len(gc.get_referents(untracked_capsule)), 0)
gc.get_referents(tracked_capsule)
+ @cpython_only
+ def test_get_objects_during_gc(self):
+ # gh-125859: Calling gc.get_objects() or gc.get_referrers() during a
+ # collection should not crash.
+ test = self
+ collected = False
+
+ class GetObjectsOnDel:
+ def __del__(self):
+ nonlocal collected
+ collected = True
+ objs = gc.get_objects()
+ # NB: can't use "in" here because some objects override __eq__
+ for obj in objs:
+ test.assertTrue(obj is not self)
+ test.assertEqual(gc.get_referrers(self), [])
+
+ obj = GetObjectsOnDel()
+ obj.cycle = obj
+ del obj
+
+ gc.collect()
+ self.assertTrue(collected)
class IncrementalGCTests(unittest.TestCase):