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]