From: Pedram Karimi <147748351+pedramkarimii@users.noreply.github.com> Date: Sat, 1 Aug 2026 09:57:14 +0000 (+0330) Subject: gh-151627: fix thread safety of OrderedDict iterator creation (#151688) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=86dab7c55bb85269b764886ceda1f85e3fdc7a7d;p=thirdparty%2FPython%2Fcpython.git gh-151627: fix thread safety of OrderedDict iterator creation (#151688) --- diff --git a/Lib/test/test_free_threading/test_collections.py b/Lib/test/test_free_threading/test_collections.py index 849b0480e232..a0a73c965d07 100644 --- a/Lib/test/test_free_threading/test_collections.py +++ b/Lib/test/test_free_threading/test_collections.py @@ -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 index 000000000000..2c5d39eec123 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-19-08-30-00.gh-issue-151627.xY7kLm.rst @@ -0,0 +1,2 @@ +Fix a crash in :class:`collections.OrderedDict` iterators in free-threaded +builds when the dictionary is concurrently cleared or updated. diff --git a/Objects/odictobject.c b/Objects/odictobject.c index c51fd4b7195d..a860ade3b148 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -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;