Skip to content

Commit 10dba24

Browse files
committed
Add additional unit tests for free-threading.
1 parent 95078c8 commit 10dba24

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# It's most useful to run these tests with ThreadSanitizer enabled.
2+
import sys
3+
import functools
4+
import threading
5+
import time
6+
import unittest
7+
8+
from test.support import threading_helper
9+
10+
11+
class TestBase(unittest.TestCase):
12+
pass
13+
14+
15+
def do_race(func1, func2):
16+
"""Run func1() and func2() repeatedly in separate threads."""
17+
n = 1000
18+
19+
def repeat(func):
20+
for _i in range(n):
21+
func()
22+
23+
threads = [
24+
threading.Thread(target=functools.partial(repeat, func1)),
25+
threading.Thread(target=functools.partial(repeat, func2)),
26+
]
27+
for thread in threads:
28+
thread.start()
29+
for thread in threads:
30+
thread.join()
31+
32+
33+
@threading_helper.requires_working_threading()
34+
class TestRaces(TestBase):
35+
def test_racing_cell_set(self):
36+
"""Test cell object gettr/settr properties."""
37+
38+
def nested_func():
39+
x = 0
40+
41+
def inner():
42+
nonlocal x
43+
x += 1
44+
45+
# This doesn't race because LOAD_DEREF and STORE_DEREF on the
46+
# cell object use critical sections.
47+
do_race(nested_func, nested_func)
48+
49+
def nested_func2():
50+
x = 0
51+
52+
def inner():
53+
y = x
54+
frame = sys._getframe(1)
55+
frame.f_locals["x"] = 2
56+
57+
return inner
58+
59+
def mutate_func2():
60+
inner = nested_func2()
61+
cell = inner.__closure__[0]
62+
old_value = cell.cell_contents
63+
cell.cell_contents = 1000
64+
time.sleep(0)
65+
cell.cell_contents = old_value
66+
time.sleep(0)
67+
68+
# This revealed a race with cell_set_contents() since it was missing
69+
# the critical section.
70+
do_race(nested_func2, mutate_func2)
71+
72+
def test_racing_cell_cmp_repr(self):
73+
"""Test cell object compare and repr methods."""
74+
75+
def nested_func():
76+
x = 0
77+
y = 0
78+
79+
def inner():
80+
return x + y
81+
82+
return inner.__closure__
83+
84+
cell_a, cell_b = nested_func()
85+
86+
def mutate():
87+
cell_a.cell_contents += 1
88+
89+
def access():
90+
cell_a == cell_b
91+
s = repr(cell_a)
92+
93+
# cell_richcompare() and cell_repr used to have data races
94+
do_race(mutate, access)
95+
96+
def test_racing_load_super_attr(self):
97+
"""Test (un)specialization of LOAD_SUPER_ATTR opcode."""
98+
99+
class C:
100+
def __init__(self):
101+
try:
102+
super().__init__
103+
super().__init__()
104+
except RuntimeError:
105+
pass # happens if __class__ is replaced with non-type
106+
107+
def access():
108+
C()
109+
110+
def mutate():
111+
# Swap out the super() global with a different one
112+
real_super = super
113+
globals()["super"] = lambda s=1: s
114+
time.sleep(0)
115+
globals()["super"] = real_super
116+
time.sleep(0)
117+
# Swap out the __class__ closure value with a non-type
118+
cell = C.__init__.__closure__[0]
119+
real_class = cell.cell_contents
120+
cell.cell_contents = 99
121+
time.sleep(0)
122+
cell.cell_contents = real_class
123+
124+
# The initial PR adding specialized opcodes for LOAD_SUPER_ATTR
125+
# had some races (one with the super() global changing and one
126+
# with the cell binding being changed).
127+
do_race(access, mutate)
128+
129+
130+
if __name__ == "__main__":
131+
unittest.main()

0 commit comments

Comments
 (0)