Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ec46a55

Browse filesBrowse files
authored
gh-121464: Make concurrent iteration over enumerate safe under free-threading (#125734)
1 parent 7ea6e88 commit ec46a55
Copy full SHA for ec46a55

File tree

3 files changed

+77
-20
lines changed
Filter options

3 files changed

+77
-20
lines changed
+38Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import unittest
2+
import sys
3+
from threading import Thread, Barrier
4+
5+
from test.support import threading_helper
6+
7+
threading_helper.requires_working_threading(module=True)
8+
9+
class EnumerateThreading(unittest.TestCase):
10+
11+
@threading_helper.reap_threads
12+
def test_threading(self):
13+
number_of_threads = 10
14+
number_of_iterations = 8
15+
n = 100
16+
start = sys.maxsize - 40
17+
barrier = Barrier(number_of_threads)
18+
def work(enum):
19+
barrier.wait()
20+
while True:
21+
try:
22+
_ = next(enum)
23+
except StopIteration:
24+
break
25+
26+
for it in range(number_of_iterations):
27+
enum = enumerate(tuple(range(start, start + n)))
28+
worker_threads = []
29+
for ii in range(number_of_threads):
30+
worker_threads.append(
31+
Thread(target=work, args=[enum]))
32+
with threading_helper.start_threads(worker_threads):
33+
pass
34+
35+
barrier.reset()
36+
37+
if __name__ == "__main__":
38+
unittest.main()
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make concurrent iterations over the same :func:`enumerate` iterator safe under free-threading. See `Strategy for Iterators in Free Threading <https://github.com/python/cpython/issues/124397>`_.

‎Objects/enumobject.c

Copy file name to clipboardExpand all lines: Objects/enumobject.c
+38-20Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -171,32 +171,45 @@ enum_traverse(PyObject *op, visitproc visit, void *arg)
171171
return 0;
172172
}
173173

174+
// increment en_longindex with lock held, return the next index to be used
175+
// or NULL on error
176+
static inline PyObject *
177+
increment_longindex_lock_held(enumobject *en)
178+
{
179+
PyObject *next_index = en->en_longindex;
180+
if (next_index == NULL) {
181+
next_index = PyLong_FromSsize_t(PY_SSIZE_T_MAX);
182+
if (next_index == NULL) {
183+
return NULL;
184+
}
185+
}
186+
assert(next_index != NULL);
187+
PyObject *stepped_up = PyNumber_Add(next_index, en->one);
188+
if (stepped_up == NULL) {
189+
return NULL;
190+
}
191+
en->en_longindex = stepped_up;
192+
return next_index;
193+
}
194+
174195
static PyObject *
175196
enum_next_long(enumobject *en, PyObject* next_item)
176197
{
177198
PyObject *result = en->en_result;
178199
PyObject *next_index;
179-
PyObject *stepped_up;
180200
PyObject *old_index;
181201
PyObject *old_item;
182202

183-
if (en->en_longindex == NULL) {
184-
en->en_longindex = PyLong_FromSsize_t(PY_SSIZE_T_MAX);
185-
if (en->en_longindex == NULL) {
186-
Py_DECREF(next_item);
187-
return NULL;
188-
}
189-
}
190-
next_index = en->en_longindex;
191-
assert(next_index != NULL);
192-
stepped_up = PyNumber_Add(next_index, en->one);
193-
if (stepped_up == NULL) {
203+
204+
Py_BEGIN_CRITICAL_SECTION(en);
205+
next_index = increment_longindex_lock_held(en);
206+
Py_END_CRITICAL_SECTION();
207+
if (next_index == NULL) {
194208
Py_DECREF(next_item);
195209
return NULL;
196210
}
197-
en->en_longindex = stepped_up;
198211

199-
if (Py_REFCNT(result) == 1) {
212+
if (_PyObject_IsUniquelyReferenced(result)) {
200213
Py_INCREF(result);
201214
old_index = PyTuple_GET_ITEM(result, 0);
202215
old_item = PyTuple_GET_ITEM(result, 1);
@@ -237,17 +250,18 @@ enum_next(PyObject *op)
237250
if (next_item == NULL)
238251
return NULL;
239252

240-
if (en->en_index == PY_SSIZE_T_MAX)
253+
Py_ssize_t en_index = FT_ATOMIC_LOAD_SSIZE_RELAXED(en->en_index);
254+
if (en_index == PY_SSIZE_T_MAX)
241255
return enum_next_long(en, next_item);
242256

243-
next_index = PyLong_FromSsize_t(en->en_index);
257+
next_index = PyLong_FromSsize_t(en_index);
244258
if (next_index == NULL) {
245259
Py_DECREF(next_item);
246260
return NULL;
247261
}
248-
en->en_index++;
262+
FT_ATOMIC_STORE_SSIZE_RELAXED(en->en_index, en_index + 1);
249263

250-
if (Py_REFCNT(result) == 1) {
264+
if (_PyObject_IsUniquelyReferenced(result)) {
251265
Py_INCREF(result);
252266
old_index = PyTuple_GET_ITEM(result, 0);
253267
old_item = PyTuple_GET_ITEM(result, 1);
@@ -277,10 +291,14 @@ static PyObject *
277291
enum_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
278292
{
279293
enumobject *en = _enumobject_CAST(op);
294+
PyObject *result;
295+
Py_BEGIN_CRITICAL_SECTION(en);
280296
if (en->en_longindex != NULL)
281-
return Py_BuildValue("O(OO)", Py_TYPE(en), en->en_sit, en->en_longindex);
297+
result = Py_BuildValue("O(OO)", Py_TYPE(en), en->en_sit, en->en_longindex);
282298
else
283-
return Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en->en_index);
299+
result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en->en_index);
300+
Py_END_CRITICAL_SECTION();
301+
return result;
284302
}
285303

286304
PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.