Skip to content

Commit 6b8ce5b

Browse files
DinoVSonicField
authored andcommitted
pythongh-118415: Fix issues with local tracing being enabled/disabled on a function (python#118496)
1 parent e561e4f commit 6b8ce5b

File tree

2 files changed

+71
-59
lines changed

2 files changed

+71
-59
lines changed

Lib/test/test_free_threading/test_monitoring.py

+26-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, _PyRLock
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,28 @@ 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+
def trace(frame, event, arg):
223+
frame.f_trace_opcodes = True
224+
return trace
225+
226+
sys.settrace(trace)
227+
try:
228+
l = _PyRLock()
229+
230+
def f():
231+
for i in range(3000):
232+
with l:
233+
pass
234+
235+
t = Thread(target=f)
236+
t.start()
237+
for i in range(3000):
238+
with l:
239+
pass
240+
t.join()
241+
finally:
242+
sys.settrace(None)
230243

231244
if __name__ == "__main__":
232245
unittest.main()

Python/instrumentation.c

+45-46
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 int
1004+
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
}
@@ -1218,8 +1224,7 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
12181224
{
12191225
PyCodeObject *code = _PyFrame_GetCode(frame);
12201226
assert(tstate->tracing == 0);
1221-
assert(is_version_up_to_date(code, tstate->interp));
1222-
assert(instrumentation_cross_checks(tstate->interp, code));
1227+
assert(debug_check_sanity(tstate->interp, code));
12231228
int i = (int)(instr - _PyCode_CODE(code));
12241229

12251230
_PyCoMonitoringData *monitoring = code->_co_monitoring;
@@ -1339,8 +1344,7 @@ int
13391344
_Py_call_instrumentation_instruction(PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr)
13401345
{
13411346
PyCodeObject *code = _PyFrame_GetCode(frame);
1342-
assert(is_version_up_to_date(code, tstate->interp));
1343-
assert(instrumentation_cross_checks(tstate->interp, code));
1347+
assert(debug_check_sanity(tstate->interp, code));
13441348
int offset = (int)(instr - _PyCode_CODE(code));
13451349
_PyCoMonitoringData *instrumentation_data = code->_co_monitoring;
13461350
assert(instrumentation_data->per_instruction_opcodes);
@@ -1671,9 +1675,11 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
16711675
PyErr_NoMemory();
16721676
return -1;
16731677
}
1674-
/* This may not be necessary, as we can initialize this memory lazily, but it helps catch errors. */
1678+
// Initialize all of the instructions so if local events change while another thread is executing
1679+
// we know what the original opcode was.
16751680
for (int i = 0; i < code_len; i++) {
1676-
code->_co_monitoring->per_instruction_opcodes[i] = 0;
1681+
int opcode = _PyCode_CODE(code)[i].op.code;
1682+
code->_co_monitoring->per_instruction_opcodes[i] = _PyOpcode_Deopt[opcode];
16771683
}
16781684
}
16791685
if (multitools && code->_co_monitoring->per_instruction_tools == NULL) {
@@ -1682,7 +1688,6 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
16821688
PyErr_NoMemory();
16831689
return -1;
16841690
}
1685-
/* This may not be necessary, as we can initialize this memory lazily, but it helps catch errors. */
16861691
for (int i = 0; i < code_len; i++) {
16871692
code->_co_monitoring->per_instruction_tools[i] = 0;
16881693
}
@@ -1692,17 +1697,10 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
16921697
}
16931698

16941699
static int
1695-
instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
1700+
force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
16961701
{
16971702
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
16981703

1699-
if (is_version_up_to_date(code, interp)) {
1700-
assert(
1701-
interp->ceval.instrumentation_version == 0 ||
1702-
instrumentation_cross_checks(interp, code)
1703-
);
1704-
return 0;
1705-
}
17061704
#ifdef _Py_TIER2
17071705
if (code->co_executors != NULL) {
17081706
_PyCode_Clear_Executors(code);
@@ -1769,17 +1767,14 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17691767

17701768
// GH-103845: We need to remove both the line and instruction instrumentation before
17711769
// adding new ones, otherwise we may remove the newly added instrumentation.
1772-
17731770
uint8_t removed_line_tools = removed_events.tools[PY_MONITORING_EVENT_LINE];
17741771
uint8_t removed_per_instruction_tools = removed_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
17751772

17761773
if (removed_line_tools) {
17771774
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
17781775
for (int i = code->_co_firsttraceable; i < code_len;) {
17791776
if (line_data[i].original_opcode) {
1780-
if (removed_line_tools) {
1781-
remove_line_tools(code, i, removed_line_tools);
1782-
}
1777+
remove_line_tools(code, i, removed_line_tools);
17831778
}
17841779
i += _PyInstruction_GetLength(code, i);
17851780
}
@@ -1791,25 +1786,22 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17911786
i += _PyInstruction_GetLength(code, i);
17921787
continue;
17931788
}
1794-
if (removed_per_instruction_tools) {
1795-
remove_per_instruction_tools(code, i, removed_per_instruction_tools);
1796-
}
1789+
remove_per_instruction_tools(code, i, removed_per_instruction_tools);
17971790
i += _PyInstruction_GetLength(code, i);
17981791
}
17991792
}
18001793
#ifdef INSTRUMENT_DEBUG
18011794
sanity_check_instrumentation(code);
18021795
#endif
1796+
18031797
uint8_t new_line_tools = new_events.tools[PY_MONITORING_EVENT_LINE];
18041798
uint8_t new_per_instruction_tools = new_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
18051799

18061800
if (new_line_tools) {
18071801
_PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
18081802
for (int i = code->_co_firsttraceable; i < code_len;) {
18091803
if (line_data[i].original_opcode) {
1810-
if (new_line_tools) {
1811-
add_line_tools(code, i, new_line_tools);
1812-
}
1804+
add_line_tools(code, i, new_line_tools);
18131805
}
18141806
i += _PyInstruction_GetLength(code, i);
18151807
}
@@ -1821,12 +1813,11 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
18211813
i += _PyInstruction_GetLength(code, i);
18221814
continue;
18231815
}
1824-
if (new_per_instruction_tools) {
1825-
add_per_instruction_tools(code, i, new_per_instruction_tools);
1826-
}
1816+
add_per_instruction_tools(code, i, new_per_instruction_tools);
18271817
i += _PyInstruction_GetLength(code, i);
18281818
}
18291819
}
1820+
18301821
done:
18311822
FT_ATOMIC_STORE_UINTPTR_RELEASE(code->_co_instrumentation_version,
18321823
global_version(interp));
@@ -1837,6 +1828,22 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
18371828
return 0;
18381829
}
18391830

1831+
static int
1832+
instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
1833+
{
1834+
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
1835+
1836+
if (is_version_up_to_date(code, interp)) {
1837+
assert(
1838+
interp->ceval.instrumentation_version == 0 ||
1839+
instrumentation_cross_checks(interp, code)
1840+
);
1841+
return 0;
1842+
}
1843+
1844+
return force_instrument_lock_held(code, interp);
1845+
}
1846+
18401847
int
18411848
_Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
18421849
{
@@ -1983,16 +1990,8 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
19831990
goto done;
19841991
}
19851992
set_local_events(local, tool_id, events);
1986-
if (is_version_up_to_date(code, interp)) {
1987-
/* Force instrumentation update */
1988-
code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT;
1989-
}
1990-
1991-
#ifdef _Py_TIER2
1992-
_Py_Executors_InvalidateDependency(interp, code, 1);
1993-
#endif
19941993

1995-
res = instrument_lock_held(code, interp);
1994+
res = force_instrument_lock_held(code, interp);
19961995

19971996
done:
19981997
UNLOCK_CODE();

0 commit comments

Comments
 (0)