]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151627: fix thread safety of OrderedDict iterator creation (#151688)
authorPedram Karimi <147748351+pedramkarimii@users.noreply.github.com>
Sat, 1 Aug 2026 09:57:14 +0000 (13:27 +0330)
committerGitHub <noreply@github.com>
Sat, 1 Aug 2026 09:57:14 +0000 (15:27 +0530)
Lib/test/test_free_threading/test_collections.py
Misc/NEWS.d/next/Library/2026-06-19-08-30-00.gh-issue-151627.xY7kLm.rst [new file with mode: 0644]
Objects/odictobject.c

index 849b0480e232fc284c92ccacb5664f6006cc55ed..a0a73c965d072a2c69c1c3230c4a7c427c5976c8 100644 (file)
@@ -1,5 +1,5 @@
 import unittest
-from collections import deque
+from collections import OrderedDict, deque
 from copy import copy
 from test.support import threading_helper
 
@@ -49,5 +49,30 @@ class TestDeque(unittest.TestCase):
         )
 
 
+class TestOrderedDict(unittest.TestCase):
+    def test_iterator_update_clear_race(self):
+        # gh-151627: OrderedDict iterator construction must not race with
+        # concurrent clear()/update() operations that mutate the linked list.
+        od = OrderedDict((i, i) for i in range(100))
+
+        def mutate():
+            for i in range(5000):
+                od.clear()
+                od.update(((i, i), (i + 1, i + 1), (i + 2, i + 2)))
+
+        def iterate():
+            for _ in range(5000):
+                try:
+                    for _ in od:
+                        pass
+                    list(reversed(od))
+                except RuntimeError:
+                    pass
+
+        threading_helper.run_concurrently(
+            [mutate, *[iterate for _ in range(8)]],
+        )
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-06-19-08-30-00.gh-issue-151627.xY7kLm.rst b/Misc/NEWS.d/next/Library/2026-06-19-08-30-00.gh-issue-151627.xY7kLm.rst
new file mode 100644 (file)
index 0000000..2c5d39e
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a crash in :class:`collections.OrderedDict` iterators in free-threaded
+builds when the dictionary is concurrently cleared or updated.
index c51fd4b7195da42f43d3b347fe1803e963ce5478..a860ade3b148e5fc78e4f772b45e65c58a82805d 100644 (file)
@@ -1958,11 +1958,13 @@ odictiter_new(PyODictObject *od, int kind)
     }
 
     di->kind = kind;
+    Py_BEGIN_CRITICAL_SECTION(od);
     node = reversed ? _odict_LAST(od) : _odict_FIRST(od);
     di->di_current = node ? Py_NewRef(_odictnode_KEY(node)) : NULL;
     di->di_size = PyODict_SIZE(od);
     di->di_state = od->od_state;
     di->di_odict = (PyODictObject*)Py_NewRef(od);
+    Py_END_CRITICAL_SECTION();
 
     _PyObject_GC_TRACK(di);
     return (PyObject *)di;