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