From 8df9f79695a86092a6983197a74e6c666d48cc86 Mon Sep 17 00:00:00 2001 From: Lu Xiaowei Date: Fri, 24 Jul 2026 11:32:24 +0800 Subject: [PATCH 1/3] gh-154756: Fix data race in list.sort() --- Objects/listobject.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 8a9c9bda68269b8..f56ed82c96fa36a 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -1653,25 +1653,25 @@ typedef struct { Py_LOCAL_INLINE(void) sortslice_copy(sortslice *s1, Py_ssize_t i, sortslice *s2, Py_ssize_t j) { - s1->keys[i] = s2->keys[j]; + FT_ATOMIC_STORE_PTR_RELEASE(s1->keys[i], s2->keys[j]); if (s1->values != NULL) - s1->values[i] = s2->values[j]; + FT_ATOMIC_STORE_PTR_RELEASE(s1->values[i], s2->values[j]); } Py_LOCAL_INLINE(void) sortslice_copy_incr(sortslice *dst, sortslice *src) { - *dst->keys++ = *src->keys++; + FT_ATOMIC_STORE_PTR_RELEASE(*dst->keys++, *src->keys++); if (dst->values != NULL) - *dst->values++ = *src->values++; + FT_ATOMIC_STORE_PTR_RELEASE(*dst->values++, *src->values++); } Py_LOCAL_INLINE(void) sortslice_copy_decr(sortslice *dst, sortslice *src) { - *dst->keys-- = *src->keys--; + FT_ATOMIC_STORE_PTR_RELEASE(*dst->keys--, *src->keys--); if (dst->values != NULL) - *dst->values-- = *src->values--; + FT_ATOMIC_STORE_PTR_RELEASE(*dst->values--, *src->values--); } @@ -1855,22 +1855,22 @@ binarysort(MergeState *ms, const sortslice *ss, Py_ssize_t n, Py_ssize_t ok) for (M = ok - 1; M >= 0; --M) { k = ISLT(pivot, a[M]); if (k < 0) { - a[M + 1] = pivot; + FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], pivot); if (has_values) - v[M + 1] = vpivot; + FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], vpivot); goto fail; } else if (k) { - a[M + 1] = a[M]; + FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], a[M]); if (has_values) - v[M + 1] = v[M]; + FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], v[M]); } else break; } - a[M + 1] = pivot; + FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], pivot); if (has_values) - v[M + 1] = vpivot; + FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], vpivot); } #else // binary insertion sort Py_ssize_t L, R; @@ -1915,13 +1915,13 @@ binarysort(MergeState *ms, const sortslice *ss, Py_ssize_t n, Py_ssize_t ok) usually moving many slots. Years later: under Visual Studio 2022, memmove seems just slightly slower than doing it "by hand". */ for (M = ok; M > L; --M) - a[M] = a[M - 1]; - a[L] = pivot; + FT_ATOMIC_STORE_PTR_RELEASE(a[M], a[M - 1]); + FT_ATOMIC_STORE_PTR_RELEASE(a[L], pivot); if (has_values) { pivot = v[ok]; for (M = ok; M > L; --M) - v[M] = v[M - 1]; - v[L] = pivot; + FT_ATOMIC_STORE_PTR_RELEASE(v[M], v[M - 1]); + FT_ATOMIC_STORE_PTR_RELEASE(v[L], pivot); } } #endif // pick binary or regular insertion sort From b9ed966fa439a0f7256b234eddd34e00d03d2c87 Mon Sep 17 00:00:00 2001 From: Lu Xiaowei Date: Mon, 24 Aug 2026 19:35:44 +0800 Subject: [PATCH 2/3] Revert "gh-154756: Fix data race in list.sort()" This reverts commit 8df9f79695a86092a6983197a74e6c666d48cc86. --- Objects/listobject.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index f56ed82c96fa36a..8a9c9bda68269b8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -1653,25 +1653,25 @@ typedef struct { Py_LOCAL_INLINE(void) sortslice_copy(sortslice *s1, Py_ssize_t i, sortslice *s2, Py_ssize_t j) { - FT_ATOMIC_STORE_PTR_RELEASE(s1->keys[i], s2->keys[j]); + s1->keys[i] = s2->keys[j]; if (s1->values != NULL) - FT_ATOMIC_STORE_PTR_RELEASE(s1->values[i], s2->values[j]); + s1->values[i] = s2->values[j]; } Py_LOCAL_INLINE(void) sortslice_copy_incr(sortslice *dst, sortslice *src) { - FT_ATOMIC_STORE_PTR_RELEASE(*dst->keys++, *src->keys++); + *dst->keys++ = *src->keys++; if (dst->values != NULL) - FT_ATOMIC_STORE_PTR_RELEASE(*dst->values++, *src->values++); + *dst->values++ = *src->values++; } Py_LOCAL_INLINE(void) sortslice_copy_decr(sortslice *dst, sortslice *src) { - FT_ATOMIC_STORE_PTR_RELEASE(*dst->keys--, *src->keys--); + *dst->keys-- = *src->keys--; if (dst->values != NULL) - FT_ATOMIC_STORE_PTR_RELEASE(*dst->values--, *src->values--); + *dst->values-- = *src->values--; } @@ -1855,22 +1855,22 @@ binarysort(MergeState *ms, const sortslice *ss, Py_ssize_t n, Py_ssize_t ok) for (M = ok - 1; M >= 0; --M) { k = ISLT(pivot, a[M]); if (k < 0) { - FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], pivot); + a[M + 1] = pivot; if (has_values) - FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], vpivot); + v[M + 1] = vpivot; goto fail; } else if (k) { - FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], a[M]); + a[M + 1] = a[M]; if (has_values) - FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], v[M]); + v[M + 1] = v[M]; } else break; } - FT_ATOMIC_STORE_PTR_RELEASE(a[M + 1], pivot); + a[M + 1] = pivot; if (has_values) - FT_ATOMIC_STORE_PTR_RELEASE(v[M + 1], vpivot); + v[M + 1] = vpivot; } #else // binary insertion sort Py_ssize_t L, R; @@ -1915,13 +1915,13 @@ binarysort(MergeState *ms, const sortslice *ss, Py_ssize_t n, Py_ssize_t ok) usually moving many slots. Years later: under Visual Studio 2022, memmove seems just slightly slower than doing it "by hand". */ for (M = ok; M > L; --M) - FT_ATOMIC_STORE_PTR_RELEASE(a[M], a[M - 1]); - FT_ATOMIC_STORE_PTR_RELEASE(a[L], pivot); + a[M] = a[M - 1]; + a[L] = pivot; if (has_values) { pivot = v[ok]; for (M = ok; M > L; --M) - FT_ATOMIC_STORE_PTR_RELEASE(v[M], v[M - 1]); - FT_ATOMIC_STORE_PTR_RELEASE(v[L], pivot); + v[M] = v[M - 1]; + v[L] = pivot; } } #endif // pick binary or regular insertion sort From 5b39f2525482dd7b191fada42f0b9c0e7acb671d Mon Sep 17 00:00:00 2001 From: Lu Xiaowei Date: Thu, 30 Jul 2026 16:43:24 +0800 Subject: [PATCH 3/3] gh-154756: Fix data race in list.sort() --- Objects/listobject.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Objects/listobject.c b/Objects/listobject.c index 8a9c9bda68269b8..1982966e56c1387 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2968,6 +2968,22 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) saved_ob_size = Py_SIZE(self); saved_ob_item = self->ob_item; saved_allocated = self->allocated; +#ifdef Py_GIL_DISABLED + // We can't use in-place sort for free-threaded, because list can be concurrently + // read by other threads, leading to data race. + PyObject **orig_ob_item = saved_ob_item; + if (saved_ob_size >= 2 && _PyObject_GC_IS_SHARED(self)) { + // sort only take place when size >= 2, see `nremaining` + _PyListArray *sort_array = list_allocate_array((size_t)saved_allocated); + if (sort_array == NULL) { + PyErr_NoMemory(); + return NULL; + } + memcpy(sort_array->ob_item, saved_ob_item, + (size_t)saved_ob_size * sizeof(PyObject *)); + saved_ob_item = sort_array->ob_item; + } +#endif Py_SET_SIZE(self, 0); FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item, NULL); self->allocated = -1; /* any operation will reset it to >= 0 */ @@ -3202,6 +3218,13 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) #endif free_list_items(final_ob_item, use_qsbr); } +#ifdef Py_GIL_DISABLED + if (saved_ob_item != orig_ob_item) { + // Only release memory, don't decref, because elements are owned by the published sorted array now. + ensure_shared_on_resize(self); + free_list_items(orig_ob_item, _PyObject_GC_IS_SHARED(self)); + } +#endif return Py_XNewRef(result); } #undef IFLT