Skip to content

Commit 4029242

Browse files
[3.14] gh-153932: protect read of en_index during enumerate.reduce (GH-154118) (GH-154129)
gh-153932: protect read of en_index during enumerate.reduce (GH-154118) (cherry picked from commit 1604043) Co-authored-by: L. Le <lelynn11@gmail.com>
1 parent 2b807eb commit 4029242

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

Lib/test/libregrtest/tsan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'test_ctypes',
1111
# 'test_concurrent_futures', # gh-130605: too many data races
1212
'test_enum',
13+
'test_enumerate',
1314
'test_functools',
1415
'test_httpservers',
1516
'test_imaplib',

Lib/test/test_enumerate.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import sys
44
import pickle
55
import gc
6+
import threading
7+
68

79
from test import support
10+
from test.support import threading_helper
811

912
class G:
1013
'Sequence using __getitem__'
@@ -292,5 +295,28 @@ def enum(self, iterable, start=sys.maxsize + 1):
292295
(sys.maxsize+3,'c')]
293296

294297

298+
@threading_helper.requires_working_threading()
299+
class TestThreadSafety(EnumerateStartTestCase):
300+
def test_thread_safety_while_iterating(self):
301+
# gh-153932: calling reduce while iterating should pass with TSAN
302+
303+
en = enumerate(range(10_000))
304+
stop = threading.Event()
305+
306+
def advance():
307+
for _ in en:
308+
pass
309+
stop.set()
310+
311+
def read():
312+
while not stop.is_set():
313+
en.__reduce__()
314+
315+
threads = [threading.Thread(target=advance), threading.Thread(target=read)]
316+
317+
with threading_helper.start_threads(threads):
318+
pass
319+
320+
295321
if __name__ == "__main__":
296322
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix thread safety issue in the ``__reduce__`` method of
2+
:py:class:`enumerate`.

Objects/enumobject.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,13 @@ enum_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
291291
enumobject *en = _enumobject_CAST(op);
292292
PyObject *result;
293293
Py_BEGIN_CRITICAL_SECTION(en);
294-
if (en->en_longindex != NULL)
294+
if (en->en_longindex != NULL) {
295295
result = Py_BuildValue("O(OO)", Py_TYPE(en), en->en_sit, en->en_longindex);
296-
else
297-
result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en->en_index);
296+
}
297+
else {
298+
Py_ssize_t en_index = FT_ATOMIC_LOAD_SSIZE_RELAXED(en->en_index);
299+
result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en_index);
300+
}
298301
Py_END_CRITICAL_SECTION();
299302
return result;
300303
}

0 commit comments

Comments
 (0)