]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-150750: Fix a race condition in `deque.index` with free-threading (#150779)
authorsobolevn <mail@sobolevn.me>
Thu, 4 Jun 2026 13:31:31 +0000 (16:31 +0300)
committerGitHub <noreply@github.com>
Thu, 4 Jun 2026 13:31:31 +0000 (13:31 +0000)
Lib/test/test_deque.py
Lib/test/test_free_threading/test_collections.py
Misc/NEWS.d/next/Library/2026-06-02-14-21-46.gh-issue-150750.SVS2o0.rst [new file with mode: 0644]
Modules/_collectionsmodule.c
Modules/clinic/_collectionsmodule.c.h
Modules/posixmodule.c

index 4e1a489205a6855e6a3f065d756ca8085d4b1043..3c45032cda91387747385d8de4c86654ad874938 100644 (file)
@@ -287,6 +287,22 @@ class TestBasic(unittest.TestCase):
                     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):
index 3a413ccf396d4ba3b1a3345dccbba2e38f14bad6..849b0480e232fc284c92ccacb5664f6006cc55ed 100644 (file)
@@ -24,6 +24,30 @@ class TestDeque(unittest.TestCase):
 
         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 (file)
index 0000000..bda5003
--- /dev/null
@@ -0,0 +1 @@
+Fix a race condition in :meth:`collections.deque.index` with free-threading.
index c5d4879312bc8a8bf824cf4600fbc24576e153ca..5ca6362406a78b9c57227fe5784cf4de7d9190d5 100644 (file)
@@ -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;
index b5c315c680e78217ee68438d5b4410e66b6dba21..6c60678a6fbd51a0a2966cf2995684dff63b3cd0 100644 (file)
@@ -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]*/
index a06479fa60cdba19ce39e1c0e5b01d3e7acfbb4a..f9f53ca5cb5e49bcad9a6d2c9bf8ad6aa0d083cf 100644 (file)
@@ -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]