Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 40ef815

Browse files
committed
tracing thread safety
1 parent bb66600 commit 40ef815

File tree

8 files changed

+457
-86
lines changed

8 files changed

+457
-86
lines changed

Include/internal/pycore_ceval_state.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct _ceval_runtime_state {
6363
} perf;
6464
/* Pending calls to be made only on the main thread. */
6565
struct _pending_calls pending_mainthread;
66+
PyMutex sys_trace_profile_mutex;
6667
};
6768

6869
#ifdef PY_HAVE_PERF_TRAMPOLINE

Include/internal/pycore_pyatomic_ft_wrappers.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,25 @@ extern "C" {
2323
#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value)
2424
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \
2525
_Py_atomic_load_ssize_relaxed(&value)
26+
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) \
27+
_Py_atomic_load_ptr_acquire(&value)
2628
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
2729
_Py_atomic_store_ptr_relaxed(&value, new_value)
2830
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \
2931
_Py_atomic_store_ptr_release(&value, new_value)
3032
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \
3133
_Py_atomic_store_ssize_relaxed(&value, new_value)
34+
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) \
35+
_Py_atomic_store_uint8_relaxed(&value, new_value)
3236
#else
3337
#define FT_ATOMIC_LOAD_SSIZE(value) value
3438
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
39+
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) value
3540
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
3641
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
3742
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value
43+
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value
44+
3845
#endif
3946

4047
#ifdef __cplusplus
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import os
2+
from test import support
3+
4+
def load_tests(*args):
5+
return support.load_package_tests(os.path.dirname(__file__), *args)
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""Tests monitoring, sys.settrace, and sys.setprofile in a multi-threaded
2+
environmenet to verify things are thread-safe in a free-threaded build"""
3+
4+
import sys
5+
import unittest
6+
import weakref
7+
8+
from sys import monitoring
9+
from threading import Thread
10+
from unittest import TestCase
11+
12+
13+
class InstrumentationMultiThreadedMixin:
14+
if not hasattr(sys, "gettotalrefcount"):
15+
thread_count = 50
16+
func_count = 1000
17+
fib = 15
18+
else:
19+
# Run a little faster in debug builds...
20+
thread_count = 25
21+
func_count = 500
22+
fib = 15
23+
24+
def after_threads(self):
25+
"""Runs once after all the threads have started"""
26+
pass
27+
28+
def during_threads(self):
29+
"""Runs repeatedly while the threads are still running"""
30+
pass
31+
32+
def work(self, n, funcs):
33+
"""Fibonacci function which also calls a bunch of random functions"""
34+
for func in funcs:
35+
func()
36+
if n < 2:
37+
return n
38+
return self.work(n - 1, funcs) + self.work(n - 2, funcs)
39+
40+
def after_test(self):
41+
"""Runs once after the test is done"""
42+
pass
43+
44+
def test_instrumention(self):
45+
# Setup a bunch of functions which will need instrumentation...
46+
funcs = []
47+
for i in range(self.func_count):
48+
x = {}
49+
exec("def f(): pass", x)
50+
funcs.append(x["f"])
51+
52+
threads = []
53+
for i in range(self.thread_count):
54+
# Each thread gets a copy of the func list to avoid contention
55+
t = Thread(target=self.work, args=(self.fib, list(funcs)))
56+
t.start()
57+
threads.append(t)
58+
59+
self.after_threads()
60+
61+
while True:
62+
any_alive = False
63+
for t in threads:
64+
if t.is_alive():
65+
any_alive = True
66+
break
67+
68+
if not any_alive:
69+
break
70+
71+
self.during_threads()
72+
73+
self.after_test()
74+
75+
76+
class MonitoringTestMixin:
77+
def setUp(self):
78+
for i in range(6):
79+
if monitoring.get_tool(i) is None:
80+
self.tool_id = i
81+
monitoring.use_tool_id(i, self.__class__.__name__)
82+
break
83+
84+
def tearDown(self):
85+
monitoring.free_tool_id(self.tool_id)
86+
87+
88+
class SetPreTraceMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
89+
"""Sets tracing one time after the threads have started"""
90+
91+
def setUp(self):
92+
super().setUp()
93+
self.called = False
94+
95+
def after_test(self):
96+
self.assertTrue(self.called)
97+
98+
def trace_func(self, frame, event, arg):
99+
self.called = True
100+
return self.trace_func
101+
102+
def after_threads(self):
103+
sys.settrace(self.trace_func)
104+
105+
106+
class MonitoringMultiThreaded(
107+
MonitoringTestMixin, InstrumentationMultiThreadedMixin, TestCase
108+
):
109+
"""Uses sys.monitoring and repeatedly toggles instrumentation on and off"""
110+
111+
def setUp(self):
112+
super().setUp()
113+
self.set = False
114+
self.called = False
115+
monitoring.register_callback(
116+
self.tool_id, monitoring.events.LINE, self.callback
117+
)
118+
119+
def tearDown(self):
120+
monitoring.set_events(self.tool_id, 0)
121+
super().tearDown()
122+
123+
def callback(self, *args):
124+
self.called = True
125+
126+
def after_test(self):
127+
self.assertTrue(self.called)
128+
129+
def during_threads(self):
130+
if self.set:
131+
monitoring.set_events(
132+
self.tool_id, monitoring.events.CALL | monitoring.events.LINE
133+
)
134+
else:
135+
monitoring.set_events(self.tool_id, 0)
136+
self.set = not self.set
137+
138+
139+
class SetTraceMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
140+
"""Uses sys.settrace and repeatedly toggles instrumentation on and off"""
141+
142+
def setUp(self):
143+
self.set = False
144+
self.called = False
145+
146+
def after_test(self):
147+
self.assertTrue(self.called)
148+
149+
def tearDown(self):
150+
sys.settrace(None)
151+
152+
def trace_func(self, frame, event, arg):
153+
self.called = True
154+
return self.trace_func
155+
156+
def during_threads(self):
157+
if self.set:
158+
sys.settrace(self.trace_func)
159+
else:
160+
sys.settrace(None)
161+
self.set = not self.set
162+
163+
164+
class SetProfileMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
165+
"""Uses sys.setprofile and repeatedly toggles instrumentation on and off"""
166+
thread_count = 25
167+
func_count = 200
168+
fib = 10
169+
170+
def setUp(self):
171+
self.set = False
172+
self.called = False
173+
174+
def after_test(self):
175+
self.assertTrue(self.called)
176+
177+
def tearDown(self):
178+
sys.setprofile(None)
179+
180+
def trace_func(self, frame, event, arg):
181+
self.called = True
182+
return self.trace_func
183+
184+
def during_threads(self):
185+
if self.set:
186+
sys.setprofile(self.trace_func)
187+
else:
188+
sys.setprofile(None)
189+
self.set = not self.set
190+
191+
192+
class MonitoringMisc(MonitoringTestMixin, TestCase):
193+
def register_callback(self):
194+
def callback(*args):
195+
pass
196+
197+
for i in range(200):
198+
monitoring.register_callback(self.tool_id, monitoring.events.LINE, callback)
199+
200+
self.refs.append(weakref.ref(callback))
201+
202+
def test_register_callback(self):
203+
self.refs = []
204+
threads = []
205+
for i in range(50):
206+
t = Thread(target=self.register_callback)
207+
t.start()
208+
threads.append(t)
209+
210+
for thread in threads:
211+
thread.join()
212+
213+
monitoring.register_callback(self.tool_id, monitoring.events.LINE, None)
214+
for ref in self.refs:
215+
self.assertEqual(ref(), None)
216+
217+
218+
if __name__ == "__main__":
219+
unittest.main()

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,7 @@ TESTSUBDIRS= idlelib/idle_test \
23742374
test/test_doctest \
23752375
test/test_email \
23762376
test/test_email/data \
2377+
test/test_free_threading \
23772378
test/test_future_stmt \
23782379
test/test_gdb \
23792380
test/test_import \

0 commit comments

Comments
 (0)