From 88101755c042ca323177f4d91130e7fb02bc9218 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 4 Feb 2026 21:43:20 +0100 Subject: [PATCH 1/5] add test for pairwise --- Lib/test/test_free_threading/test_itertools.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_free_threading/test_itertools.py b/Lib/test/test_free_threading/test_itertools.py index bb6047e8669475..0a99fb0710c2c2 100644 --- a/Lib/test/test_free_threading/test_itertools.py +++ b/Lib/test/test_free_threading/test_itertools.py @@ -1,5 +1,5 @@ import unittest -from itertools import batched, chain, combinations_with_replacement, cycle, permutations +from itertools import batched, chain, combinations_with_replacement, cycle, pairwise, permutations from test.support import threading_helper @@ -48,6 +48,13 @@ def test_combinations_with_replacement(self): it = combinations_with_replacement(tuple(range(2)), 2) threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it]) + @threading_helper.reap_threads + def test_pairwise(self): + number_of_iterations = 10 + for _ in range(number_of_iterations): + it = pairwise(tuple(range(100))) + threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it]) + @threading_helper.reap_threads def test_permutations(self): number_of_iterations = 6 From 11e727dd28aa9f27b6d9e0888152aef3052c41f4 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 4 Feb 2026 22:07:35 +0100 Subject: [PATCH 2/5] Make itertools.pairwise safe in the FT build --- Modules/itertoolsmodule.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 7e73f76bc20b58..9631978fd9c93d 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -343,7 +343,7 @@ pairwise_traverse(PyObject *op, visitproc visit, void *arg) } static PyObject * -pairwise_next(PyObject *op) +pairwise_next_lock_held(PyObject *op) { pairwiseobject *po = pairwiseobject_CAST(op); PyObject *it = po->it; @@ -380,7 +380,7 @@ pairwise_next(PyObject *op) Py_INCREF(result); PyObject *last_old = PyTuple_GET_ITEM(result, 0); PyObject *last_new = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, Py_NewRef(old)); + PyTuple_SET_ITEM(result, 0, old); PyTuple_SET_ITEM(result, 1, Py_NewRef(new)); Py_DECREF(last_old); Py_DECREF(last_new); @@ -391,13 +391,25 @@ pairwise_next(PyObject *op) else { result = PyTuple_New(2); if (result != NULL) { - PyTuple_SET_ITEM(result, 0, Py_NewRef(old)); + PyTuple_SET_ITEM(result, 0, old); PyTuple_SET_ITEM(result, 1, Py_NewRef(new)); } + else { + Py_DECREF(old); + } } Py_XSETREF(po->old, new); - Py_DECREF(old); + return result; +} + +static PyObject * +pairwise_next(PyObject *op) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(op); + result = pairwise_next_lock_held(op); + Py_END_CRITICAL_SECTION() return result; } From a91f13ceab22568bdead16c9f4188e6e4302d4cc Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 4 Feb 2026 22:12:32 +0100 Subject: [PATCH 3/5] add comment for incref rationale --- Modules/itertoolsmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 9631978fd9c93d..b02b5a1f856cfa 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -366,7 +366,7 @@ pairwise_next_lock_held(PyObject *op) return NULL; } } - Py_INCREF(old); + Py_INCREF(old); // needed because of reentant calls via call to the iterator new = (*Py_TYPE(it)->tp_iternext)(it); if (new == NULL) { Py_CLEAR(po->it); From 931b2f9f17ddee4c944fa840e6291289798a26ef Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 4 Feb 2026 22:14:22 +0100 Subject: [PATCH 4/5] add comment for incref rationale --- Modules/itertoolsmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 9631978fd9c93d..fe06cd5f4107a0 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -366,7 +366,7 @@ pairwise_next_lock_held(PyObject *op) return NULL; } } - Py_INCREF(old); + Py_INCREF(old); // needed because of reentant calls via call to the iterator new = (*Py_TYPE(it)->tp_iternext)(it); if (new == NULL) { Py_CLEAR(po->it); @@ -380,7 +380,7 @@ pairwise_next_lock_held(PyObject *op) Py_INCREF(result); PyObject *last_old = PyTuple_GET_ITEM(result, 0); PyObject *last_new = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, old); + PyTuple_SET_ITEM(result, 0, old); // consume the reference from old PyTuple_SET_ITEM(result, 1, Py_NewRef(new)); Py_DECREF(last_old); Py_DECREF(last_new); From a030f5ad7a50d907b28b81f982e5fba13eb09ef6 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:17:48 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-02-04-21-17-43.gh-issue-123471.tVAWYF.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-02-04-21-17-43.gh-issue-123471.tVAWYF.rst diff --git a/Misc/NEWS.d/next/Library/2026-02-04-21-17-43.gh-issue-123471.tVAWYF.rst b/Misc/NEWS.d/next/Library/2026-02-04-21-17-43.gh-issue-123471.tVAWYF.rst new file mode 100644 index 00000000000000..ceb84de8ee73d6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-04-21-17-43.gh-issue-123471.tVAWYF.rst @@ -0,0 +1 @@ +Make concurrent iteration over :class:`itertools.pairwise` safe under free-threading.