Skip to content

Commit d2d84db

Browse files
committed
fix: handle legacy monitoring API
1 parent a3b0126 commit d2d84db

File tree

2 files changed

+48
-9
lines changed

2 files changed

+48
-9
lines changed

.agents/tasks/2025/08/18-1208-tracer-trait

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,6 @@ Refer to the design-docs folder for the current planned design. Add/update files
7575
--- FOLLOW UP TASK ---
7676
1. In codetracer-python-recorder/Cargo.toml, move pyo3’s extension-module feature to an optional crate feature and enable it only for release builds.
7777
2. Update Justfile (and CI scripts) to run cargo test --no-default-features so the test binary links with the Python C library.
78-
3. Add pyo3 with auto-initialize under [dev-dependencies] if tests require the interpreter to be initialized automatically.
78+
3. Add pyo3 with auto-initialize under [dev-dependencies] if tests require the interpreter to be initialized automatically.
79+
--- FOLLOW UP TASK ---
80+
thread 'tracer_prints_on_call' panicked at tests/print_tracer.rs:21:51:\ncalled `Result::unwrap()` on an `Err` value: PyErr { type: <class 'AttributeError'>, value: AttributeError("module 'sys' has no attribute 'monitoring'"), traceback: None }\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n\n\nfailures:\n tracer_prints_on_call\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

codetracer-python-recorder/src/tracer.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use std::sync::Mutex;
22

33
use bitflags::bitflags;
44
use pyo3::{
5-
exceptions::PyRuntimeError,
5+
exceptions::{PyRuntimeError, PyTypeError},
6+
ffi,
67
prelude::*,
78
types::{PyModule, PyTuple},
8-
ffi,
99
};
1010

1111
bitflags! {
@@ -33,7 +33,9 @@ pub enum Event {
3333
/// about.
3434
pub trait Tracer: Send {
3535
/// Return the set of events the tracer wants to receive.
36-
fn interest(&self) -> EventMask { EventMask::empty() }
36+
fn interest(&self) -> EventMask {
37+
EventMask::empty()
38+
}
3739

3840
/// Called on Python function calls.
3941
fn on_call(&mut self, _py: Python<'_>, _frame: *mut ffi::PyFrameObject) {}
@@ -83,7 +85,21 @@ pub fn install_tracer(py: Python<'_>, tracer: Box<dyn Tracer>) -> PyResult<()> {
8385
return Err(PyRuntimeError::new_err("tracer already installed"));
8486
}
8587
let monitoring = py.import("sys")?.getattr("monitoring")?;
86-
let tool_id: u8 = monitoring.call_method1("use_tool_id", ("codetracer",))?.extract()?;
88+
// `use_tool_id` changed its signature between Python versions.
89+
// Try calling it with the newer single-argument form first and fall back to
90+
// the older two-argument variant if that fails with a `TypeError`.
91+
const FALLBACK_ID: u8 = 5;
92+
let tool_id: u8 = match monitoring.call_method1("use_tool_id", ("codetracer",)) {
93+
Ok(obj) => obj.extract()?,
94+
Err(err) => {
95+
if err.is_instance_of::<PyTypeError>(py) {
96+
monitoring.call_method1("use_tool_id", (FALLBACK_ID, "codetracer"))?;
97+
FALLBACK_ID
98+
} else {
99+
return Err(err);
100+
}
101+
}
102+
};
87103
let events = monitoring.getattr("events")?;
88104
let module = PyModule::new(py, "_codetracer_callbacks")?;
89105

@@ -102,9 +118,21 @@ pub fn install_tracer(py: Python<'_>, tracer: Box<dyn Tracer>) -> PyResult<()> {
102118
monitoring.call_method("register_callback", (tool_id, ev, &cb), None)?;
103119
callbacks.push(cb.unbind());
104120
}
105-
monitoring.call_method("set_events", (tool_id, mask.bits(), mask.bits()), None)?;
121+
if let Err(err) =
122+
monitoring.call_method("set_events", (tool_id, mask.bits(), mask.bits()), None)
123+
{
124+
if err.is_instance_of::<PyTypeError>(py) {
125+
monitoring.call_method1("set_events", (tool_id, mask.bits()))?;
126+
} else {
127+
return Err(err);
128+
}
129+
}
106130

107-
*guard = Some(Global { dispatcher: Dispatcher::new(tracer), tool_id, callbacks });
131+
*guard = Some(Global {
132+
dispatcher: Dispatcher::new(tracer),
133+
tool_id,
134+
callbacks,
135+
});
108136
Ok(())
109137
}
110138

@@ -122,7 +150,17 @@ pub fn uninstall_tracer(py: Python<'_>) -> PyResult<()> {
122150
let ev = events.getattr("LINE")?;
123151
monitoring.call_method("register_callback", (global.tool_id, ev, py.None()), None)?;
124152
}
125-
monitoring.call_method("set_events", (global.tool_id, 0u32, global.dispatcher.mask.bits()), None)?;
153+
if let Err(err) = monitoring.call_method(
154+
"set_events",
155+
(global.tool_id, 0u32, global.dispatcher.mask.bits()),
156+
None,
157+
) {
158+
if err.is_instance_of::<PyTypeError>(py) {
159+
monitoring.call_method1("set_events", (global.tool_id, 0u32))?;
160+
} else {
161+
return Err(err);
162+
}
163+
}
126164
monitoring.call_method1("free_tool_id", (global.tool_id,))?;
127165
}
128166
Ok(())
@@ -146,4 +184,3 @@ fn callback_line(py: Python<'_>, args: &Bound<'_, PyTuple>) -> PyResult<()> {
146184
}
147185
Ok(())
148186
}
149-

0 commit comments

Comments
 (0)