From: Pieter Eendebak Date: Mon, 30 Jun 2025 11:06:58 +0000 (+0200) Subject: gh-123471: Make itertools.chain thread-safe (#135689) X-Git-Tag: v3.15.0a1~1138 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0533c1faf27d1e50b062bb623dfad93288757f57;p=thirdparty%2FPython%2Fcpython.git gh-123471: Make itertools.chain thread-safe (#135689) --- diff --git a/Lib/test/test_free_threading/test_itertools.py b/Lib/test/test_free_threading/test_itertools.py index b8663ade1d4a..9d366041917b 100644 --- a/Lib/test/test_free_threading/test_itertools.py +++ b/Lib/test/test_free_threading/test_itertools.py @@ -1,6 +1,6 @@ import unittest from threading import Thread, Barrier -from itertools import batched, cycle +from itertools import batched, chain, cycle from test.support import threading_helper @@ -17,7 +17,7 @@ class ItertoolsThreading(unittest.TestCase): barrier.wait() while True: try: - _ = next(it) + next(it) except StopIteration: break @@ -62,6 +62,34 @@ class ItertoolsThreading(unittest.TestCase): barrier.reset() + @threading_helper.reap_threads + def test_chain(self): + number_of_threads = 6 + number_of_iterations = 20 + + barrier = Barrier(number_of_threads) + def work(it): + barrier.wait() + while True: + try: + next(it) + except StopIteration: + break + + data = [(1, )] * 200 + for it in range(number_of_iterations): + chain_iterator = chain(*data) + worker_threads = [] + for ii in range(number_of_threads): + worker_threads.append( + Thread(target=work, args=[chain_iterator])) + + with threading_helper.start_threads(worker_threads): + pass + + barrier.reset() + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst b/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst new file mode 100644 index 000000000000..6f395024a9e1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst @@ -0,0 +1 @@ +Make concurrent iterations over :class:`itertools.chain` safe under :term:`free threading`. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 2003546ce84c..e6536c250109 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -1880,8 +1880,8 @@ chain_traverse(PyObject *op, visitproc visit, void *arg) return 0; } -static PyObject * -chain_next(PyObject *op) +static inline PyObject * +chain_next_lock_held(PyObject *op) { chainobject *lz = chainobject_CAST(op); PyObject *item; @@ -1919,6 +1919,16 @@ chain_next(PyObject *op) return NULL; } +static PyObject * +chain_next(PyObject *op) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(op); + result = chain_next_lock_held(op); + Py_END_CRITICAL_SECTION() + return result; +} + PyDoc_STRVAR(chain_doc, "chain(*iterables)\n\ --\n\