Description
Bug report
Bug description:
A bunch of the instrumentation state is per-code object, such as the active montiors. The modifications also typically happen lazily when a code object is executed after instrumentation is enabled/disabled.
cpython/Python/instrumentation.c
Lines 1812 to 1814 in 0240ef4
However, if you create a new thread, then it will be initialized without the instrumented bytecodes. Here's an example that breaks:
- Enable instrumentation and call some function. This will replace things like
CALL
withINSTRUMENTED_CALL
. - Disable instrumentation. Note that this doesn't immediately change
INSTRUMENTED_CALL
back toCALL
! - Start a new thread, enable instrumentation, and call that same function - uh oh!
In (3), the new thread gets a clean copy of the bytecode without instrumentation:
Lines 3333 to 3341 in 0240ef4
However, the code object still has instrumentation enabled, so the monitors_are_empty
check above returns with instrumenting the bytecode. Missing events!
Adapted from @pablogsal's repro:
import sys
import threading
import dis
def looooooser(x):
print("I am a looooooser")
def LOSER():
looooooser(42)
TRACES = []
def tracing_function(frame, event, arg):
function_name = frame.f_code.co_name
TRACES.append((function_name, event, arg))
def func1():
sys.setprofile(tracing_function)
LOSER()
sys.setprofile(None)
TRACES.clear()
def func2():
def thread_body():
sys.setprofile(tracing_function)
LOSER()
sys.setprofile(None)
dis.dis(looooooser, adaptive=True)
# WHEN
bg_thread = threading.Thread(target=thread_body)
bg_thread.start()
bg_thread.join()
for trace in TRACES:
print(trace)
assert ('looooooser', 'call', None) in TRACES
func1()
func2()
cc @mpage
CPython versions tested on:
CPython main branch, 3.14, 3.15
Operating systems tested on:
No response