From c82bf62723065fade1e93a4c0b8d00c81154b863 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Fri, 12 Jun 2026 17:36:36 +0200 Subject: [PATCH 1/3] fix: critical section for islice_next --- Modules/itertoolsmodule.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 0dd31dfbc5a3469..cc1ad0a39033432 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -1625,8 +1625,8 @@ islice_traverse(PyObject *op, visitproc visit, void *arg) return 0; } -static PyObject * -islice_next(PyObject *op) +static inline PyObject * +islice_next_lock_held(PyObject *op) { isliceobject *lz = isliceobject_CAST(op); PyObject *item; @@ -1665,6 +1665,16 @@ islice_next(PyObject *op) return NULL; } +static PyObject * +islice_next(PyObject *op) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(op); + result = islice_next_lock_held(op); + Py_END_CRITICAL_SECTION(); + return result; +} + PyDoc_STRVAR(islice_doc, "islice(iterable, stop) --> islice object\n\ islice(iterable, start, stop[, step]) --> islice object\n\ From 1b65c9c4aef6c4bf9a98d9d0fbd25beb8d2daf23 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Fri, 12 Jun 2026 17:44:06 +0200 Subject: [PATCH 2/3] test: add regression test --- Lib/test/test_itertools.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index cf579d4da4e0dfb..02ac97a1bc9f68a 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -2489,6 +2489,34 @@ def __eq__(self, other): for j in range(2): next(g, None) # shouldn't crash + @threading_helper.requires_working_threading() + def test_islice_thread_safety(self): + # gh-151409: islice must not yield more items than its stop argument + # when consumed concurrently from multiple threads. + STOP = 100 + NTHREADS = 8 + data = iter(range(STOP + NTHREADS * 2)) + sl = islice(data, STOP) + results: list[int] = [] + lock = threading.Lock() + + def consume() -> None: + while True: + v = next(sl, None) + if v is None: + break + with lock: + results.append(v) + + threads = [threading.Thread(target=consume) for _ in range(NTHREADS)] + for t in threads: + t.start() + for t in threads: + t.join() + + self.assertLessEqual(len(results), STOP) + self.assertEqual(sorted(results), list(range(len(results)))) + class SubclassWithKwargsTest(unittest.TestCase): def test_keywords_in_subclass(self): From decc6304aecec83057dd50cd8ecd7854458f1643 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Fri, 12 Jun 2026 17:45:52 +0200 Subject: [PATCH 3/3] misc: news entry --- .../next/Library/2026-06-12-15-44-31.gh-issue-151409.QAx38E.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-12-15-44-31.gh-issue-151409.QAx38E.rst diff --git a/Misc/NEWS.d/next/Library/2026-06-12-15-44-31.gh-issue-151409.QAx38E.rst b/Misc/NEWS.d/next/Library/2026-06-12-15-44-31.gh-issue-151409.QAx38E.rst new file mode 100644 index 000000000000000..6bbde82922d04ac --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-12-15-44-31.gh-issue-151409.QAx38E.rst @@ -0,0 +1 @@ +Fix :class:`itertools.islice` to be thread-safe when shared across threads.