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

Lines changed: 4 additions & 0 deletions
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
}
Lines changed: 80 additions & 0 deletions
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

Lines changed: 6 additions & 3 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy