https://github.com/python/cpython/commit/f54d44d3331ef0bc19f1437f32ffde2e49c07c22
commit: f54d44d3331ef0bc19f1437f32ffde2e49c07c22
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2025-12-18T13:11:51-05:00
summary:

gh-129068: Make range iterators thread-safe (gh-142886)

Now that we specialize range iteration in the interpreter for the common
case where the iterator has only one reference, there's not a
significant performance cost to making the iteration thread-safe.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-10-49-03.gh-issue-129068.GlYnrO.rst
A Objects/clinic/rangeobject.c.h
M Objects/rangeobject.c
M Tools/tsan/suppressions_free_threading.txt

diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-10-49-03.gh-issue-129068.GlYnrO.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-10-49-03.gh-issue-129068.GlYnrO.rst
new file mode 100644
index 00000000000000..16b19524cd4057
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-10-49-03.gh-issue-129068.GlYnrO.rst
@@ -0,0 +1,2 @@
+Make concurrent iteration over the same range iterator thread-safe in the
+free threading build.
diff --git a/Objects/clinic/rangeobject.c.h b/Objects/clinic/rangeobject.c.h
new file mode 100644
index 00000000000000..d9142eddde4b17
--- /dev/null
+++ b/Objects/clinic/rangeobject.c.h
@@ -0,0 +1,150 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
+
+PyDoc_STRVAR(range_iterator___length_hint____doc__,
+"__length_hint__($self, /)\n"
+"--\n"
+"\n"
+"Private method returning an estimate of len(list(it)).");
+
+#define RANGE_ITERATOR___LENGTH_HINT___METHODDEF    \
+    {"__length_hint__", (PyCFunction)range_iterator___length_hint__, 
METH_NOARGS, range_iterator___length_hint____doc__},
+
+static PyObject *
+range_iterator___length_hint___impl(_PyRangeIterObject *r);
+
+static PyObject *
+range_iterator___length_hint__(PyObject *r, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = range_iterator___length_hint___impl((_PyRangeIterObject 
*)r);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(range_iterator___reduce____doc__,
+"__reduce__($self, /)\n"
+"--\n"
+"\n"
+"Return state information for pickling.");
+
+#define RANGE_ITERATOR___REDUCE___METHODDEF    \
+    {"__reduce__", (PyCFunction)range_iterator___reduce__, METH_NOARGS, 
range_iterator___reduce____doc__},
+
+static PyObject *
+range_iterator___reduce___impl(_PyRangeIterObject *r);
+
+static PyObject *
+range_iterator___reduce__(PyObject *r, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = range_iterator___reduce___impl((_PyRangeIterObject *)r);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(range_iterator___setstate____doc__,
+"__setstate__($self, state, /)\n"
+"--\n"
+"\n"
+"Set state information for unpickling.");
+
+#define RANGE_ITERATOR___SETSTATE___METHODDEF    \
+    {"__setstate__", (PyCFunction)range_iterator___setstate__, METH_O, 
range_iterator___setstate____doc__},
+
+static PyObject *
+range_iterator___setstate___impl(_PyRangeIterObject *r, PyObject *state);
+
+static PyObject *
+range_iterator___setstate__(PyObject *r, PyObject *state)
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = range_iterator___setstate___impl((_PyRangeIterObject *)r, 
state);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(longrange_iterator___length_hint____doc__,
+"__length_hint__($self, /)\n"
+"--\n"
+"\n"
+"Private method returning an estimate of len(list(it)).");
+
+#define LONGRANGE_ITERATOR___LENGTH_HINT___METHODDEF    \
+    {"__length_hint__", (PyCFunction)longrange_iterator___length_hint__, 
METH_NOARGS, longrange_iterator___length_hint____doc__},
+
+static PyObject *
+longrange_iterator___length_hint___impl(longrangeiterobject *r);
+
+static PyObject *
+longrange_iterator___length_hint__(PyObject *r, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = 
longrange_iterator___length_hint___impl((longrangeiterobject *)r);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(longrange_iterator___reduce____doc__,
+"__reduce__($self, /)\n"
+"--\n"
+"\n"
+"Return state information for pickling.");
+
+#define LONGRANGE_ITERATOR___REDUCE___METHODDEF    \
+    {"__reduce__", (PyCFunction)longrange_iterator___reduce__, METH_NOARGS, 
longrange_iterator___reduce____doc__},
+
+static PyObject *
+longrange_iterator___reduce___impl(longrangeiterobject *r);
+
+static PyObject *
+longrange_iterator___reduce__(PyObject *r, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = longrange_iterator___reduce___impl((longrangeiterobject 
*)r);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(longrange_iterator___setstate____doc__,
+"__setstate__($self, state, /)\n"
+"--\n"
+"\n"
+"Set state information for unpickling.");
+
+#define LONGRANGE_ITERATOR___SETSTATE___METHODDEF    \
+    {"__setstate__", (PyCFunction)longrange_iterator___setstate__, METH_O, 
longrange_iterator___setstate____doc__},
+
+static PyObject *
+longrange_iterator___setstate___impl(longrangeiterobject *r, PyObject *state);
+
+static PyObject *
+longrange_iterator___setstate__(PyObject *r, PyObject *state)
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(r);
+    return_value = longrange_iterator___setstate___impl((longrangeiterobject 
*)r, state);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+/*[clinic end generated code: output=719c0e4c81fe0f4a input=a9049054013a1b77]*/
diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c
index e93346fb27703f..55b7f108730728 100644
--- a/Objects/rangeobject.c
+++ b/Objects/rangeobject.c
@@ -9,6 +9,21 @@
 #include "pycore_range.h"
 #include "pycore_tuple.h"         // _PyTuple_ITEMS()
 
+typedef struct {
+    PyObject_HEAD
+    PyObject *start;
+    PyObject *step;
+    PyObject *len;
+} longrangeiterobject;
+
+/*[clinic input]
+class range_iterator "_PyRangeIterObject *" "&PyRangeIter_Type"
+class longrange_iterator "longrangeiterobject *" "&PyLongRangeIter_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c7d97a63d1cfa6b3]*/
+
+#include "clinic/rangeobject.c.h"
+
 
 /* Support objects whose length is > PY_SSIZE_T_MAX.
 
@@ -830,30 +845,46 @@ PyTypeObject PyRange_Type = {
 static PyObject *
 rangeiter_next(PyObject *op)
 {
+    PyObject *ret = NULL;
+    Py_BEGIN_CRITICAL_SECTION(op);
     _PyRangeIterObject *r = (_PyRangeIterObject*)op;
     if (r->len > 0) {
         long result = r->start;
         r->start = result + r->step;
         r->len--;
-        return PyLong_FromLong(result);
+        ret = PyLong_FromLong(result);
     }
-    return NULL;
+    Py_END_CRITICAL_SECTION();
+    return ret;
 }
 
+/*[clinic input]
+@critical_section
+range_iterator.__length_hint__
+    self as r: self(type="_PyRangeIterObject *")
+
+Private method returning an estimate of len(list(it)).
+[clinic start generated code]*/
+
 static PyObject *
-rangeiter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
+range_iterator___length_hint___impl(_PyRangeIterObject *r)
+/*[clinic end generated code: output=9ba6f22b1fc23dcc input=e3eb311e99d76e43]*/
 {
-    _PyRangeIterObject *r = (_PyRangeIterObject*)op;
     return PyLong_FromLong(r->len);
 }
 
-PyDoc_STRVAR(length_hint_doc,
-             "Private method returning an estimate of len(list(it)).");
+/*[clinic input]
+@critical_section
+range_iterator.__reduce__
+    self as r: self(type="_PyRangeIterObject *")
+
+Return state information for pickling.
+[clinic start generated code]*/
 
 static PyObject *
-rangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
+range_iterator___reduce___impl(_PyRangeIterObject *r)
+/*[clinic end generated code: output=c44d53750c388415 input=75a25b7076dc2c54]*/
 {
-    _PyRangeIterObject *r = (_PyRangeIterObject*)op;
     PyObject *start=NULL, *stop=NULL, *step=NULL;
     PyObject *range;
 
@@ -881,10 +912,20 @@ rangeiter_reduce(PyObject *op, PyObject 
*Py_UNUSED(ignored))
     return NULL;
 }
 
+/*[clinic input]
+@critical_section
+range_iterator.__setstate__
+    self as r: self(type="_PyRangeIterObject *")
+    state: object
+    /
+
+Set state information for unpickling.
+[clinic start generated code]*/
+
 static PyObject *
-rangeiter_setstate(PyObject *op, PyObject *state)
+range_iterator___setstate___impl(_PyRangeIterObject *r, PyObject *state)
+/*[clinic end generated code: output=464b3cbafc2e3562 input=c8c84fab2519d200]*/
 {
-    _PyRangeIterObject *r = (_PyRangeIterObject*)op;
     long index = PyLong_AsLong(state);
     if (index == -1 && PyErr_Occurred())
         return NULL;
@@ -904,13 +945,10 @@ rangeiter_dealloc(PyObject *self)
     _Py_FREELIST_FREE(range_iters, (_PyRangeIterObject *)self, PyObject_Free);
 }
 
-PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");
-PyDoc_STRVAR(setstate_doc, "Set state information for unpickling.");
-
 static PyMethodDef rangeiter_methods[] = {
-    {"__length_hint__", rangeiter_len, METH_NOARGS, length_hint_doc},
-    {"__reduce__", rangeiter_reduce, METH_NOARGS, reduce_doc},
-    {"__setstate__", rangeiter_setstate, METH_O, setstate_doc},
+    RANGE_ITERATOR___LENGTH_HINT___METHODDEF
+    RANGE_ITERATOR___REDUCE___METHODDEF
+    RANGE_ITERATOR___SETSTATE___METHODDEF
     {NULL,              NULL}           /* sentinel */
 };
 
@@ -995,25 +1033,34 @@ fast_range_iter(long start, long stop, long step, long 
len)
     return (PyObject *)it;
 }
 
-typedef struct {
-    PyObject_HEAD
-    PyObject *start;
-    PyObject *step;
-    PyObject *len;
-} longrangeiterobject;
+/*[clinic input]
+@critical_section
+longrange_iterator.__length_hint__
+    self as r: self(type="longrangeiterobject *")
+
+Private method returning an estimate of len(list(it)).
+[clinic start generated code]*/
 
 static PyObject *
-longrangeiter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
+longrange_iterator___length_hint___impl(longrangeiterobject *r)
+/*[clinic end generated code: output=e1bce24da7e8bfde input=ba94b050d940411e]*/
 {
-    longrangeiterobject *r = (longrangeiterobject*)op;
     Py_INCREF(r->len);
     return r->len;
 }
 
+/*[clinic input]
+@critical_section
+longrange_iterator.__reduce__
+    self as r: self(type="longrangeiterobject *")
+
+Return state information for pickling.
+[clinic start generated code]*/
+
 static PyObject *
-longrangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
+longrange_iterator___reduce___impl(longrangeiterobject *r)
+/*[clinic end generated code: output=0077f94ae2a4e99a input=2e8930e897ace086]*/
 {
-    longrangeiterobject *r = (longrangeiterobject*)op;
     PyObject *product, *stop=NULL;
     PyObject *range;
 
@@ -1039,15 +1086,25 @@ longrangeiter_reduce(PyObject *op, PyObject 
*Py_UNUSED(ignored))
                          range, Py_None);
 }
 
+/*[clinic input]
+@critical_section
+longrange_iterator.__setstate__
+    self as r: self(type="longrangeiterobject *")
+    state: object
+    /
+
+Set state information for unpickling.
+[clinic start generated code]*/
+
 static PyObject *
-longrangeiter_setstate(PyObject *op, PyObject *state)
+longrange_iterator___setstate___impl(longrangeiterobject *r, PyObject *state)
+/*[clinic end generated code: output=870787f0574f0da4 input=8b116de3018de824]*/
 {
     if (!PyLong_CheckExact(state)) {
         PyErr_Format(PyExc_TypeError, "state must be an int, not %T", state);
         return NULL;
     }
 
-    longrangeiterobject *r = (longrangeiterobject*)op;
     PyObject *zero = _PyLong_GetZero();  // borrowed reference
     int cmp;
 
@@ -1085,9 +1142,9 @@ longrangeiter_setstate(PyObject *op, PyObject *state)
 }
 
 static PyMethodDef longrangeiter_methods[] = {
-    {"__length_hint__", longrangeiter_len, METH_NOARGS, length_hint_doc},
-    {"__reduce__", longrangeiter_reduce, METH_NOARGS, reduce_doc},
-    {"__setstate__", longrangeiter_setstate, METH_O, setstate_doc},
+    LONGRANGE_ITERATOR___LENGTH_HINT___METHODDEF
+    LONGRANGE_ITERATOR___REDUCE___METHODDEF
+    LONGRANGE_ITERATOR___SETSTATE___METHODDEF
     {NULL,              NULL}           /* sentinel */
 };
 
@@ -1102,7 +1159,7 @@ longrangeiter_dealloc(PyObject *op)
 }
 
 static PyObject *
-longrangeiter_next(PyObject *op)
+longrangeiter_next_lock_held(PyObject *op)
 {
     longrangeiterobject *r = (longrangeiterobject*)op;
     if (PyObject_RichCompareBool(r->len, _PyLong_GetZero(), Py_GT) != 1)
@@ -1123,6 +1180,16 @@ longrangeiter_next(PyObject *op)
     return result;
 }
 
+static PyObject *
+longrangeiter_next(PyObject *op)
+{
+    PyObject *result;
+    Py_BEGIN_CRITICAL_SECTION(op);
+    result = longrangeiter_next_lock_held(op);
+    Py_END_CRITICAL_SECTION();
+    return result;
+}
+
 PyTypeObject PyLongRangeIter_Type = {
         PyVarObject_HEAD_INIT(&PyType_Type, 0)
         "longrange_iterator",                   /* tp_name */
diff --git a/Tools/tsan/suppressions_free_threading.txt 
b/Tools/tsan/suppressions_free_threading.txt
index 46489f5cd9dbae..adc85d631db7c6 100644
--- a/Tools/tsan/suppressions_free_threading.txt
+++ b/Tools/tsan/suppressions_free_threading.txt
@@ -20,15 +20,9 @@ race_top:_PyObject_TryGetInstanceAttribute
 race_top:PyUnstable_InterpreterFrame_GetLine
 race_top:write_thread_id
 
-# gh-129068: race on shared range iterators 
(test_free_threading.test_zip.ZipThreading.test_threading)
-race_top:rangeiter_next
-
 # https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40
 thread:pthread_create
 
-# Range iteration is not thread-safe yet (issue #129068)
-race_top:rangeiter_next
-
 # List resizing happens through different paths ending in memcpy or memmove
 # (for efficiency), which will probably need to rewritten as explicit loops
 # of ptr-sized copies to be thread-safe. (Issue #129069)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to