Skip to content

Commit 855157f

Browse files
committed
Lock around sanity assertions and always maintain original opcodes so tools can be set/removed locally
1 parent 3b3f8de commit 855157f

File tree

2 files changed

+76
-60
lines changed

2 files changed

+76
-60
lines changed

Lib/test/test_free_threading/test_monitoring.py

+27-13
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,14 @@
88

99
from sys import monitoring
1010
from test.support import is_wasi
11-
from threading import Thread
11+
from threading import Thread, _RLock
1212
from unittest import TestCase
1313

1414

1515
class InstrumentationMultiThreadedMixin:
16-
if not hasattr(sys, "gettotalrefcount"):
17-
thread_count = 50
18-
func_count = 1000
19-
fib = 15
20-
else:
21-
# Run a little faster in debug builds...
22-
thread_count = 25
23-
func_count = 500
24-
fib = 15
16+
thread_count = 10
17+
func_count = 200
18+
fib = 12
2519

2620
def after_threads(self):
2721
"""Runs once after all the threads have started"""
@@ -175,9 +169,6 @@ def during_threads(self):
175169
@unittest.skipIf(is_wasi, "WASI has no threads.")
176170
class SetProfileMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
177171
"""Uses sys.setprofile and repeatedly toggles instrumentation on and off"""
178-
thread_count = 25
179-
func_count = 200
180-
fib = 15
181172

182173
def setUp(self):
183174
self.set = False
@@ -227,6 +218,29 @@ def test_register_callback(self):
227218
for ref in self.refs:
228219
self.assertEqual(ref(), None)
229220

221+
def test_set_local_trace_opcodes(self):
222+
return
223+
def trace(frame, event, arg):
224+
frame.f_trace_opcodes = True
225+
return trace
226+
227+
sys.settrace(trace)
228+
try:
229+
l = _RLock()
230+
231+
def f():
232+
for i in range(3000):
233+
with l:
234+
pass
235+
236+
t = threading.Thread(target=f)
237+
t.start()
238+
for i in range(3000):
239+
with l:
240+
pass
241+
t.join()
242+
finally:
243+
sys.settrace(None)
230244

231245
if __name__ == "__main__":
232246
unittest.main()

Python/instrumentation.c

+49-47
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,6 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
459459
}
460460

461461
#define CHECK(test) do { \
462-
ASSERT_WORLD_STOPPED_OR_LOCKED(code); \
463462
if (!(test)) { \
464463
dump_instrumentation_data(code, i, stderr); \
465464
} \
@@ -516,10 +515,6 @@ sanity_check_instrumentation(PyCodeObject *code)
516515
if (!is_instrumented(opcode)) {
517516
CHECK(_PyOpcode_Deopt[opcode] == opcode);
518517
}
519-
if (data->per_instruction_tools) {
520-
uint8_t tools = active_monitors.tools[PY_MONITORING_EVENT_INSTRUCTION];
521-
CHECK((tools & data->per_instruction_tools[i]) == data->per_instruction_tools[i]);
522-
}
523518
}
524519
if (opcode == INSTRUMENTED_LINE) {
525520
CHECK(data->lines);
@@ -677,8 +672,6 @@ de_instrument_per_instruction(PyCodeObject *code, int i)
677672
}
678673
assert(*opcode_ptr != INSTRUMENTED_INSTRUCTION);
679674
assert(instr->op.code != INSTRUMENTED_INSTRUCTION);
680-
/* Keep things clean for sanity check */
681-
code->_co_monitoring->per_instruction_opcodes[i] = 0;
682675
}
683676

684677

@@ -992,18 +985,32 @@ set_global_version(PyThreadState *tstate, uint32_t version)
992985
static bool
993986
is_version_up_to_date(PyCodeObject *code, PyInterpreterState *interp)
994987
{
988+
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
995989
return global_version(interp) == code->_co_instrumentation_version;
996990
}
997991

998992
#ifndef NDEBUG
999993
static bool
1000994
instrumentation_cross_checks(PyInterpreterState *interp, PyCodeObject *code)
1001995
{
996+
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
1002997
_Py_LocalMonitors expected = local_union(
1003998
interp->monitors,
1004999
code->_co_monitoring->local_monitors);
10051000
return monitors_equals(code->_co_monitoring->active_monitors, expected);
10061001
}
1002+
1003+
static inline
1004+
int debug_check_sanity(PyInterpreterState *interp, PyCodeObject *code)
1005+
{
1006+
int res;
1007+
LOCK_CODE(code);
1008+
res = is_version_up_to_date(code, interp) &&
1009+
instrumentation_cross_checks(interp, code);
1010+
UNLOCK_CODE();
1011+
return res;
1012+
}
1013+
10071014
#endif
10081015

10091016
static inline uint8_t
@@ -1018,8 +1025,7 @@ get_tools_for_instruction(PyCodeObject *code, PyInterpreterState *interp, int i,
10181025
event = PY_MONITORING_EVENT_CALL;
10191026
}
10201027
if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
1021-
CHECK(is_version_up_to_date(code, interp));
1022-
CHECK(instrumentation_cross_checks(interp, code));
1028+
CHECK(debug_check_sanity(interp, code));
10231029
if (code->_co_monitoring->tools) {
10241030
tools = code->_co_monitoring->tools[i];
10251031
}
@@ -1217,8 +1223,7 @@ int
12171223
_Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *prev)
12181224
{
12191225
PyCodeObject *code = _PyFrame_GetCode(frame);
1220-
assert(is_version_up_to_date(code, tstate->interp));
1221-
assert(instrumentation_cross_checks(tstate->interp, code));
1226+
assert(debug_check_sanity(tstate->interp, code));
12221227
int i = (int)(instr - _PyCode_CODE(code));
12231228

12241229
_PyCoMonitoringData *monitoring = code->_co_monitoring;
@@ -1341,8 +1346,7 @@ int
13411346
_Py_call_instrumentation_instruction(PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr)
13421347
{
13431348
PyCodeObject *code = _PyFrame_GetCode(frame);
1344-
assert(is_version_up_to_date(code, tstate->interp));
1345-
assert(instrumentation_cross_checks(tstate->interp, code));
1349+
assert(debug_check_sanity(tstate->interp, code));
13461350
int offset = (int)(instr - _PyCode_CODE(code));
13471351
_PyCoMonitoringData *instrumentation_data = code->_co_monitoring;
13481352
assert(instrumentation_data->per_instruction_opcodes);
@@ -1673,9 +1677,11 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
16731677
PyErr_NoMemory();
16741678
return -1;
16751679
}
1676-
/* This may not be necessary, as we can initialize this memory lazily, but it helps catch errors. */
1680+
// Initialize all of the instructions so if local events change while another thread is executing
1681+
// we know what the original opcode was.
16771682
for (int i = 0; i < code_len; i++) {
1678-
code->_co_monitoring->per_instruction_opcodes[i] = 0;
1683+
int opcode = _PyCode_CODE(code)[i].op.code;
1684+
code->_co_monitoring->per_instruction_opcodes[i] = _PyOpcode_Deopt[opcode];
16791685
}
16801686
}
16811687
if (multitools && code->_co_monitoring->per_instruction_tools == NULL) {
@@ -1684,27 +1690,22 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
16841690
PyErr_NoMemory();
16851691
return -1;
16861692
}
1687-
/* This may not be necessary, as we can initialize this memory lazily, but it helps catch errors. */
1693+
// Initialize all of the instructions so if local events change while another thread is executing
1694+
// we know what the original opcode was.
16881695
for (int i = 0; i < code_len; i++) {
1689-
code->_co_monitoring->per_instruction_tools[i] = 0;
1696+
int opcode = _PyCode_CODE(code)[i].op.code;
1697+
code->_co_monitoring->per_instruction_tools[i] = _PyOpcode_Deopt[opcode];
16901698
}
16911699
}
16921700
}
16931701
return 0;
16941702
}
16951703

16961704
static int
1697-
instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
1705+
force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
16981706
{
16991707
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
17001708

1701-
if (is_version_up_to_date(code, interp)) {
1702-
assert(
1703-
interp->ceval.instrumentation_version == 0 ||
1704-
instrumentation_cross_checks(interp, code)
1705-
);
1706-
return 0;
1707-
}
17081709
#ifdef _Py_TIER2
17091710
if (code->co_executors != NULL) {
17101711
_PyCode_Clear_Executors(code);
@@ -1771,17 +1772,14 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17711772

17721773
// GH-103845: We need to remove both the line and instruction instrumentation before
17731774
// adding new ones, otherwise we may remove the newly added instrumentation.
1774-
17751775
uint8_t removed_line_tools = removed_events.tools[PY_MONITORING_EVENT_LINE];
17761776
uint8_t removed_per_instruction_tools = removed_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
17771777

17781778
if (removed_line_tools) {
17791779
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
17801780
for (int i = code->_co_firsttraceable; i < code_len;) {
17811781
if (line_data[i].original_opcode) {
1782-
if (removed_line_tools) {
1783-
remove_line_tools(code, i, removed_line_tools);
1784-
}
1782+
remove_line_tools(code, i, removed_line_tools);
17851783
}
17861784
i += _PyInstruction_GetLength(code, i);
17871785
}
@@ -1793,25 +1791,22 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17931791
i += _PyInstruction_GetLength(code, i);
17941792
continue;
17951793
}
1796-
if (removed_per_instruction_tools) {
1797-
remove_per_instruction_tools(code, i, removed_per_instruction_tools);
1798-
}
1794+
remove_per_instruction_tools(code, i, removed_per_instruction_tools);
17991795
i += _PyInstruction_GetLength(code, i);
18001796
}
18011797
}
18021798
#ifdef INSTRUMENT_DEBUG
18031799
sanity_check_instrumentation(code);
18041800
#endif
1801+
18051802
uint8_t new_line_tools = new_events.tools[PY_MONITORING_EVENT_LINE];
18061803
uint8_t new_per_instruction_tools = new_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
18071804

18081805
if (new_line_tools) {
18091806
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
18101807
for (int i = code->_co_firsttraceable; i < code_len;) {
18111808
if (line_data[i].original_opcode) {
1812-
if (new_line_tools) {
1813-
add_line_tools(code, i, new_line_tools);
1814-
}
1809+
add_line_tools(code, i, new_line_tools);
18151810
}
18161811
i += _PyInstruction_GetLength(code, i);
18171812
}
@@ -1823,12 +1818,11 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
18231818
i += _PyInstruction_GetLength(code, i);
18241819
continue;
18251820
}
1826-
if (new_per_instruction_tools) {
1827-
add_per_instruction_tools(code, i, new_per_instruction_tools);
1828-
}
1821+
add_per_instruction_tools(code, i, new_per_instruction_tools);
18291822
i += _PyInstruction_GetLength(code, i);
18301823
}
18311824
}
1825+
18321826
done:
18331827
FT_ATOMIC_STORE_UINTPTR_RELEASE(code->_co_instrumentation_version,
18341828
global_version(interp));
@@ -1839,6 +1833,22 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
18391833
return 0;
18401834
}
18411835

1836+
static int
1837+
instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
1838+
{
1839+
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
1840+
1841+
if (is_version_up_to_date(code, interp)) {
1842+
assert(
1843+
interp->ceval.instrumentation_version == 0 ||
1844+
instrumentation_cross_checks(interp, code)
1845+
);
1846+
return 0;
1847+
}
1848+
1849+
return force_instrument_lock_held(code, interp);
1850+
}
1851+
18421852
int
18431853
_Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
18441854
{
@@ -1985,16 +1995,8 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
19851995
goto done;
19861996
}
19871997
set_local_events(local, tool_id, events);
1988-
if (is_version_up_to_date(code, interp)) {
1989-
/* Force instrumentation update */
1990-
code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT;
1991-
}
1992-
1993-
#ifdef _Py_TIER2
1994-
_Py_Executors_InvalidateDependency(interp, code, 1);
1995-
#endif
19961998

1997-
res = instrument_lock_held(code, interp);
1999+
res = force_instrument_lock_held(code, interp);
19982000

19992001
done:
20002002
UNLOCK_CODE();

0 commit comments

Comments
 (0)