gh-115999: Make list and tuple iteration more thread-safe. by Yhg1s · Pull Request #128637 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions Lib/test/test_free_threading/test_iteration.py
28 changes: 18 additions & 10 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ list_get_item_ref(PyListObject *op, Py_ssize_t i)
return NULL;
}
Py_ssize_t cap = list_capacity(ob_item);
assert(cap != -1 && cap >= size);
assert(cap != -1);
if (!valid_index(i, cap)) {
return NULL;
}
Expand Down Expand Up @@ -784,7 +784,8 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n)
_Py_RefcntAdd(*src, n);
*dest++ = *src++;
}

// TODO: _Py_memory_repeat calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
_Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);
}
Expand Down Expand Up @@ -919,6 +920,8 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
if (d < 0) { /* Delete -d items */
Py_ssize_t tail;
tail = (Py_SIZE(a) - ihigh) * sizeof(PyObject *);
// TODO: these memmove/memcpy calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
memmove(&item[ihigh+d], &item[ihigh], tail);
if (list_resize(a, Py_SIZE(a) + d) < 0) {
memmove(&item[ihigh], &item[ihigh+d], tail);
Expand All @@ -932,12 +935,14 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
if (list_resize(a, k+d) < 0)
goto Error;
item = a->ob_item;
// TODO: these memmove/memcpy calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
memmove(&item[ihigh+d], &item[ihigh],
(k - ihigh)*sizeof(PyObject *));
}
for (k = 0; k < n; k++, ilow++) {
PyObject *w = vitem[k];
item[ilow] = Py_XNewRef(w);
FT_ATOMIC_STORE_PTR_RELEASE(item[ilow], Py_XNewRef(w));
}
for (k = norig - 1; k >= 0; --k)
Py_XDECREF(recycle[k]);
Expand Down Expand Up @@ -1017,6 +1022,8 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n)
for (Py_ssize_t j = 0; j < input_size; j++) {
_Py_RefcntAdd(items[j], n-1);
}
// TODO: _Py_memory_repeat calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
_Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);
return 0;
Expand Down Expand Up @@ -3993,7 +4000,7 @@ listiter_setstate(PyObject *self, PyObject *state)
index = -1;
else if (index > PyList_GET_SIZE(it->it_seq))
index = PyList_GET_SIZE(it->it_seq); /* iterator exhausted */
it->it_index = index;
FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index);
}
Py_RETURN_NONE;
}
Expand Down Expand Up @@ -4145,7 +4152,7 @@ listreviter_setstate(PyObject *self, PyObject *state)
index = -1;
else if (index > PyList_GET_SIZE(it->it_seq) - 1)
index = PyList_GET_SIZE(it->it_seq) - 1;
it->it_index = index;
FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index);
}
Py_RETURN_NONE;
}
Expand All @@ -4162,18 +4169,19 @@ listiter_reduce_general(void *_it, int forward)
* call must be before access of iterator pointers.
* see issue #101765 */

/* the objects are not the same, index is of different types! */
if (forward) {
iter = _PyEval_GetBuiltin(&_Py_ID(iter));
_PyListIterObject *it = (_PyListIterObject *)_it;
if (it->it_index >= 0) {
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
Py_ssize_t idx = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
if (idx >= 0) {
return Py_BuildValue("N(O)n", iter, it->it_seq, idx);
}
} else {
iter = _PyEval_GetBuiltin(&_Py_ID(reversed));
listreviterobject *it = (listreviterobject *)_it;
if (it->it_index >= 0) {
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
Py_ssize_t idx = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
if (idx >= 0) {
return Py_BuildValue("N(O)n", iter, it->it_seq, idx);
}
}
/* empty iterator, create an empty list */
Expand Down
29 changes: 23 additions & 6 deletions Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1014,18 +1014,23 @@ tupleiter_next(PyObject *self)

assert(it != NULL);
seq = it->it_seq;
#ifndef Py_GIL_DISABLED
if (seq == NULL)
return NULL;
#endif
assert(PyTuple_Check(seq));

if (it->it_index < PyTuple_GET_SIZE(seq)) {
item = PyTuple_GET_ITEM(seq, it->it_index);
++it->it_index;
Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
if (index < PyTuple_GET_SIZE(seq)) {
FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index + 1);
item = PyTuple_GET_ITEM(seq, index);
return Py_NewRef(item);
}

#ifndef Py_GIL_DISABLED
Comment thread
Yhg1s marked this conversation as resolved.
it->it_seq = NULL;
Py_DECREF(seq);
#endif
return NULL;
}

Expand All @@ -1034,8 +1039,15 @@ tupleiter_len(PyObject *self, PyObject *Py_UNUSED(ignored))
{
_PyTupleIterObject *it = _PyTupleIterObject_CAST(self);
Py_ssize_t len = 0;
#ifdef Py_GIL_DISABLED
Py_ssize_t idx = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
Py_ssize_t seq_len = PyTuple_GET_SIZE(it->it_seq);
if (idx < seq_len)
len = seq_len - idx;
#else
if (it->it_seq)
len = PyTuple_GET_SIZE(it->it_seq) - it->it_index;
#endif
return PyLong_FromSsize_t(len);
}

Expand All @@ -1051,10 +1063,15 @@ tupleiter_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
* see issue #101765 */
_PyTupleIterObject *it = _PyTupleIterObject_CAST(self);

#ifdef Py_GIL_DISABLED
Py_ssize_t idx = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
if (idx < PyTuple_GET_SIZE(it->it_seq))
return Py_BuildValue("N(O)n", iter, it->it_seq, idx);
#else
if (it->it_seq)
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
else
return Py_BuildValue("N(())", iter);
#endif
return Py_BuildValue("N(())", iter);
}

static PyObject *
Expand All @@ -1069,7 +1086,7 @@ tupleiter_setstate(PyObject *self, PyObject *state)
index = 0;
else if (index > PyTuple_GET_SIZE(it->it_seq))
index = PyTuple_GET_SIZE(it->it_seq); /* exhausted iterator */
it->it_index = index;
FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index);
}
Py_RETURN_NONE;
}
Expand Down
11 changes: 9 additions & 2 deletions Tools/tsan/suppressions_free_threading.txt