Skip to content

Commit 2b4fe19

Browse files
committed
Test for efficacy of fast sequence locking macro on str.join, both tests segfault or die with assertion failures >95% of the time without locking macro, pass reliably with locking macro
1 parent b22f6e2 commit 2b4fe19

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

Include/internal/pycore_critical_section.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ extern "C" {
114114
// For performance, the argument *to* PySequence_Fast is provided to the
115115
// macro, not the *result* of PySequence_Fast (which would require an extra
116116
// test to determine if the lock must be held)
117-
# define Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(original) \
117+
# define Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(original) \
118118
{ \
119119
PyObject *_orig_seq = _PyObject_CAST(original); \
120120
const bool _should_lock_cs = PyList_CheckExact(_orig_seq); \
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import sys
2+
import unittest
3+
4+
from itertools import cycle
5+
from threading import Thread
6+
from unittest import TestCase
7+
8+
from test.support import threading_helper
9+
10+
@threading_helper.requires_working_threading()
11+
class TestStr(TestCase):
12+
def test_racing_join_extend(self):
13+
'''Test joining a string being extended by another thread'''
14+
l = []
15+
OBJECT_COUNT = 100_000
16+
17+
def writer_func():
18+
l.extend(map(str, range(OBJECT_COUNT, OBJECT_COUNT*2)))
19+
20+
def reader_func():
21+
while True:
22+
count = len(l)
23+
''.join(l)
24+
if count == OBJECT_COUNT:
25+
break
26+
27+
writer = Thread(target=writer_func)
28+
readers = []
29+
for x in range(30):
30+
reader = Thread(target=reader_func)
31+
readers.append(reader)
32+
reader.start()
33+
34+
writer.start()
35+
writer.join()
36+
for reader in readers:
37+
reader.join()
38+
39+
def test_racing_join_replace(self):
40+
'''
41+
Test joining a string of characters being replaced with ephemeral
42+
strings by another thread.
43+
'''
44+
l = [*'abcdefg']
45+
MAX_ORDINAL = 10_000
46+
47+
def writer_func():
48+
for i, c in zip(cycle(range(len(l))),
49+
map(chr, range(128, MAX_ORDINAL))):
50+
l[i] = c
51+
del l[:] # Empty list to tell readers to exit
52+
53+
def reader_func():
54+
while True:
55+
empty = not l
56+
''.join(l)
57+
if empty:
58+
break
59+
60+
writer = Thread(target=writer_func)
61+
readers = []
62+
for x in range(30):
63+
reader = Thread(target=reader_func)
64+
readers.append(reader)
65+
reader.start()
66+
67+
writer.start()
68+
writer.join()
69+
for reader in readers:
70+
reader.join()
71+
72+
73+
if __name__ == "__main__":
74+
unittest.main()

0 commit comments

Comments
 (0)