Skip to content

Commit 1e67b92

Browse files
authored
gh-117657: Fix TSAN list set failure (#118260)
* Fix TSAN list set failure * Relaxed atomic is sufficient, add targetted test * More list * Remove atomic assign in list * Fixup white space
1 parent 8ed5466 commit 1e67b92

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed

Include/internal/pycore_list.h

+4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem)
2828
Py_ssize_t allocated = self->allocated;
2929
assert((size_t)len + 1 < PY_SSIZE_T_MAX);
3030
if (allocated > len) {
31+
#ifdef Py_GIL_DISABLED
32+
_Py_atomic_store_ptr_release(&self->ob_item[len], newitem);
33+
#else
3134
PyList_SET_ITEM(self, len, newitem);
35+
#endif
3236
Py_SET_SIZE(self, len + 1);
3337
return 0;
3438
}
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import unittest
2+
3+
from threading import Thread
4+
from unittest import TestCase
5+
6+
from test.support import is_wasi
7+
8+
9+
class C:
10+
def __init__(self, v):
11+
self.v = v
12+
13+
14+
@unittest.skipIf(is_wasi, "WASI has no threads.")
15+
class TestList(TestCase):
16+
def test_racing_iter_append(self):
17+
18+
l = []
19+
OBJECT_COUNT = 10000
20+
21+
def writer_func():
22+
for i in range(OBJECT_COUNT):
23+
l.append(C(i + OBJECT_COUNT))
24+
25+
def reader_func():
26+
while True:
27+
count = len(l)
28+
for i, x in enumerate(l):
29+
self.assertEqual(x.v, i + OBJECT_COUNT)
30+
if count == OBJECT_COUNT:
31+
break
32+
33+
writer = Thread(target=writer_func)
34+
readers = []
35+
for x in range(30):
36+
reader = Thread(target=reader_func)
37+
readers.append(reader)
38+
reader.start()
39+
40+
writer.start()
41+
writer.join()
42+
for reader in readers:
43+
reader.join()
44+
45+
def test_racing_iter_extend(self):
46+
iters = [
47+
lambda x: [x],
48+
]
49+
for iter_case in iters:
50+
with self.subTest(iter=iter_case):
51+
l = []
52+
OBJECT_COUNT = 10000
53+
54+
def writer_func():
55+
for i in range(OBJECT_COUNT):
56+
l.extend(iter_case(C(i + OBJECT_COUNT)))
57+
58+
def reader_func():
59+
while True:
60+
count = len(l)
61+
for i, x in enumerate(l):
62+
self.assertEqual(x.v, i + OBJECT_COUNT)
63+
if count == OBJECT_COUNT:
64+
break
65+
66+
writer = Thread(target=writer_func)
67+
readers = []
68+
for x in range(30):
69+
reader = Thread(target=reader_func)
70+
readers.append(reader)
71+
reader.start()
72+
73+
writer.start()
74+
writer.join()
75+
for reader in readers:
76+
reader.join()
77+
78+
79+
if __name__ == "__main__":
80+
unittest.main()

Objects/listobject.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ list_resize(PyListObject *self, Py_ssize_t newsize)
141141
target_bytes = allocated * sizeof(PyObject*);
142142
}
143143
memcpy(array->ob_item, self->ob_item, target_bytes);
144+
}
145+
if (new_allocated > (size_t)allocated) {
146+
memset(array->ob_item + allocated, 0, sizeof(PyObject *) * (new_allocated - allocated));
144147
}
145148
_Py_atomic_store_ptr_release(&self->ob_item, &array->ob_item);
146149
self->allocated = new_allocated;
@@ -502,7 +505,7 @@ _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem)
502505
Py_DECREF(newitem);
503506
return -1;
504507
}
505-
PyList_SET_ITEM(self, len, newitem);
508+
FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item[len], newitem);
506509
return 0;
507510
}
508511

@@ -1181,7 +1184,7 @@ list_extend_fast(PyListObject *self, PyObject *iterable)
11811184
PyObject **dest = self->ob_item + m;
11821185
for (Py_ssize_t i = 0; i < n; i++) {
11831186
PyObject *o = src[i];
1184-
dest[i] = Py_NewRef(o);
1187+
FT_ATOMIC_STORE_PTR_RELEASE(dest[i], Py_NewRef(o));
11851188
}
11861189
return 0;
11871190
}
@@ -1238,7 +1241,7 @@ list_extend_iter_lock_held(PyListObject *self, PyObject *iterable)
12381241

12391242
if (Py_SIZE(self) < self->allocated) {
12401243
Py_ssize_t len = Py_SIZE(self);
1241-
PyList_SET_ITEM(self, len, item); // steals item ref
1244+
FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item[len], item); // steals item ref
12421245
Py_SET_SIZE(self, len + 1);
12431246
}
12441247
else {

0 commit comments

Comments
 (0)