https://github.com/python/cpython/commit/d83d50b5b735cb58e175280d1c27d40c43d535b5
commit: d83d50b5b735cb58e175280d1c27d40c43d535b5
branch: main
author: sobolevn <[email protected]>
committer: sobolevn <[email protected]>
date: 2026-06-04T13:31:31Z
summary:

gh-150750: Fix a race condition in `deque.index` with free-threading (#150779)

files:
A Misc/NEWS.d/next/Library/2026-06-02-14-21-46.gh-issue-150750.SVS2o0.rst
M Lib/test/test_deque.py
M Lib/test/test_free_threading/test_collections.py
M Modules/_collectionsmodule.c
M Modules/clinic/_collectionsmodule.c.h
M Modules/posixmodule.c

diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py
index 4e1a489205a6855..3c45032cda91387 100644
--- a/Lib/test/test_deque.py
+++ b/Lib/test/test_deque.py
@@ -287,6 +287,22 @@ def test_index(self):
                     else:
                         self.assertEqual(d.index(element, start, stop), target)
 
+        # Test stop argument
+        for elem in d:
+            index = d.index(elem)
+            self.assertEqual(
+                index,
+                d.index(elem, 0),
+            )
+            self.assertEqual(
+                index,
+                d.index(elem, 0, len(d)),
+            )
+            self.assertEqual(
+                index,
+                d.index(elem, 0, len(d) + 100),
+            )
+
         # Test large start argument
         d = deque(range(0, 10000, 10))
         for step in range(100):
diff --git a/Lib/test/test_free_threading/test_collections.py 
b/Lib/test/test_free_threading/test_collections.py
index 3a413ccf396d4ba..849b0480e232fc2 100644
--- a/Lib/test/test_free_threading/test_collections.py
+++ b/Lib/test/test_free_threading/test_collections.py
@@ -24,6 +24,30 @@ def copy_loop():
 
         threading_helper.run_concurrently([mutate, copy_loop])
 
+    def test_index_race_in_ac(self):
+        # gh-150750: There was a c_default specified as `Py_SIZE(self)`,
+        # it was used without a critical section.
+
+        d = deque(range(100))
+
+        def index():
+            for _ in range(10000):
+                try:
+                    d.index(50)
+                except ValueError:
+                    pass
+
+        def mutate():
+            for _ in range(10000):
+                d.append(0)
+                d.clear()
+                d.extend(range(100))
+                d.appendleft(-1)
+
+        threading_helper.run_concurrently(
+            [index, *[mutate for _ in range(3)]],
+        )
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-02-14-21-46.gh-issue-150750.SVS2o0.rst 
b/Misc/NEWS.d/next/Library/2026-06-02-14-21-46.gh-issue-150750.SVS2o0.rst
new file mode 100644
index 000000000000000..bda500383e7cda3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-02-14-21-46.gh-issue-150750.SVS2o0.rst
@@ -0,0 +1 @@
+Fix a race condition in :meth:`collections.deque.index` with free-threading.
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index c5d4879312bc8a8..5ca6362406a78b9 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -1251,7 +1251,7 @@ _collections.deque.index as deque_index
     deque: dequeobject
     value as v: object
     start: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', 
c_default='0') = NULL
-    stop: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', 
c_default='Py_SIZE(deque)') = NULL
+    stop: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', 
c_default='PY_SSIZE_T_MAX') = NULL
     /
 
 Return first index of value.
@@ -1262,7 +1262,7 @@ Raises ValueError if the value is not present.
 static PyObject *
 deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start,
                  Py_ssize_t stop)
-/*[clinic end generated code: output=df45132753175ef9 input=90f48833a91e1743]*/
+/*[clinic end generated code: output=df45132753175ef9 input=1c3b19632cf3484f]*/
 {
     Py_ssize_t i, n;
     PyObject *item;
@@ -1270,22 +1270,23 @@ deque_index_impl(dequeobject *deque, PyObject *v, 
Py_ssize_t start,
     Py_ssize_t index = deque->leftindex;
     size_t start_state = deque->state;
     int cmp;
+    Py_ssize_t size = Py_SIZE(deque);
 
     if (start < 0) {
-        start += Py_SIZE(deque);
+        start += size;
         if (start < 0)
             start = 0;
     }
     if (stop < 0) {
-        stop += Py_SIZE(deque);
+        stop += size;
         if (stop < 0)
             stop = 0;
     }
-    if (stop > Py_SIZE(deque))
-        stop = Py_SIZE(deque);
+    if (stop > size)
+        stop = size;
     if (start > stop)
         start = stop;
-    assert(0 <= start && start <= stop && stop <= Py_SIZE(deque));
+    assert(0 <= start && start <= stop && stop <= size);
 
     for (i=0 ; i < start - BLOCKLEN ; i += BLOCKLEN) {
         b = b->rightlink;
diff --git a/Modules/clinic/_collectionsmodule.c.h 
b/Modules/clinic/_collectionsmodule.c.h
index b5c315c680e7821..6c60678a6fbd51a 100644
--- a/Modules/clinic/_collectionsmodule.c.h
+++ b/Modules/clinic/_collectionsmodule.c.h
@@ -340,7 +340,7 @@ deque_index(PyObject *deque, PyObject *const *args, 
Py_ssize_t nargs)
     PyObject *return_value = NULL;
     PyObject *v;
     Py_ssize_t start = 0;
-    Py_ssize_t stop = Py_SIZE(deque);
+    Py_ssize_t stop = PY_SSIZE_T_MAX;
 
     if (!_PyArg_CheckPositional("index", nargs, 1, 3)) {
         goto exit;
@@ -632,4 +632,4 @@ tuplegetter_new(PyTypeObject *type, PyObject *args, 
PyObject *kwargs)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=b9d4d647c221cb9f input=a9049054013a1b77]*/
+/*[clinic end generated code: output=f5a388add99d3d15 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index a06479fa60cdba1..f9f53ca5cb5e49b 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3123,7 +3123,7 @@ class path_t_converter(CConverter):
     impl_by_reference = True
     parse_by_reference = True
     default_type = ()
-    c_init_default = "<placeholder>"  # overridden in pre_render(()
+    c_init_default = "<placeholder>"  # overridden in pre_render()
 
     converter = 'path_converter'
 
@@ -3266,7 +3266,7 @@ class confname_converter(CConverter):
         """, argname=argname, converter=self.converter, table=self.table)
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=d58f18bdf3bd3565]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=ddbf3ac90a981122]*/
 
 /*[clinic input]
 

_______________________________________________
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