Skip to content

Commit ba229fd

Browse files
committed
gh-151627: protect OrderedDict iterator creation
1 parent d701f8e commit ba229fd

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

Lib/test/test_free_threading/test_collections.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from collections import deque
2+
from collections import OrderedDict, deque
33
from copy import copy
44
from test.support import threading_helper
55

@@ -49,5 +49,30 @@ def mutate():
4949
)
5050

5151

52+
class TestOrderedDict(unittest.TestCase):
53+
def test_iterator_update_clear_race(self):
54+
# gh-151627: OrderedDict iterator construction must not race with
55+
# concurrent clear()/update() operations that mutate the linked list.
56+
od = OrderedDict((i, i) for i in range(100))
57+
58+
def mutate():
59+
for i in range(5000):
60+
od.clear()
61+
od.update(((i, i), (i + 1, i + 1), (i + 2, i + 2)))
62+
63+
def iterate():
64+
for _ in range(5000):
65+
try:
66+
for _ in od:
67+
pass
68+
list(reversed(od))
69+
except RuntimeError:
70+
pass
71+
72+
threading_helper.run_concurrently(
73+
[mutate, *[iterate for _ in range(8)]],
74+
)
75+
76+
5277
if __name__ == "__main__":
5378
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in :class:`collections.OrderedDict` iterators in free-threaded
2+
builds when the dictionary is concurrently cleared or updated.

Objects/odictobject.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,11 +1942,13 @@ odictiter_new(PyODictObject *od, int kind)
19421942
}
19431943

19441944
di->kind = kind;
1945+
Py_BEGIN_CRITICAL_SECTION(od);
19451946
node = reversed ? _odict_LAST(od) : _odict_FIRST(od);
19461947
di->di_current = node ? Py_NewRef(_odictnode_KEY(node)) : NULL;
19471948
di->di_size = PyODict_SIZE(od);
19481949
di->di_state = od->od_state;
19491950
di->di_odict = (PyODictObject*)Py_NewRef(od);
1951+
Py_END_CRITICAL_SECTION();
19501952

19511953
_PyObject_GC_TRACK(di);
19521954
return (PyObject *)di;

0 commit comments

Comments
 (0)