]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142975: During GC, mark frozen objects with a merged zero refcount for destruction...
authorPeter Bierma <zintensitydev@gmail.com>
Thu, 25 Dec 2025 16:31:41 +0000 (11:31 -0500)
committerGitHub <noreply@github.com>
Thu, 25 Dec 2025 16:31:41 +0000 (16:31 +0000)
Lib/test/test_free_threading/test_gc.py
Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst [new file with mode: 0644]
Python/gc_free_threading.c

index 3b83e0431efa6b66c76a3b285c33a11e8899d9bd..8b45b6e2150c288a17c4ead5ebb77aad060bd546 100644 (file)
@@ -62,6 +62,38 @@ class TestGC(TestCase):
         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()
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst
new file mode 100644 (file)
index 0000000..9d7f57e
--- /dev/null
@@ -0,0 +1,2 @@
+Fix crash after unfreezing all objects tracked by the garbage collector on
+the :term:`free threaded <free threading>` build.
index 04b9b8f3f85603c90ad928da5ce3cccdebf05c23..51261cea0cfe2c6e30703a87b796c894c3216a7d 100644 (file)
@@ -906,7 +906,11 @@ exit:
 static void
 queue_untracked_obj_decref(PyObject *op, struct collection_state *state)
 {
-    if (!_PyObject_GC_IS_TRACKED(op)) {
+    assert(Py_REFCNT(op) == 0);
+    // gh-142975: We have to treat frozen objects as untracked in this function
+    // or else they might be picked up in a future collection, which breaks the
+    // assumption that all incoming objects have a non-zero reference count.
+    if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) {
         // GC objects with zero refcount are handled subsequently by the
         // GC as if they were cyclic trash, but we have to handle dead
         // non-GC objects here. Add one to the refcount so that we can