Skip to content

Commit bb72285

Browse files
committed
ISSUE-003: Fail fast when capturing args fails on PY_START
- Change `Tracer::on_py_start` to return `PyResult<()>` so errors can propagate through the `#[pyfunction]` callback. - Update `callback_py_start` to return the tracer result. - Implement fail-fast in `RuntimeTracer::on_py_start`: raise a `RuntimeError` with a clear message instead of silently continuing with empty args when frame/locals access fails. - Adjust test tracers to new signature. A previous commit adds a failing pytest that monkeypatches `sys._getframe` to raise; with this change, it now passes by surfacing an exception.
1 parent 6743fa1 commit bb72285

File tree

5 files changed

+26
-21
lines changed

5 files changed

+26
-21
lines changed

codetracer-python-recorder/src/runtime_tracer.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ impl Tracer for RuntimeTracer {
135135
events_union(&[events.PY_START, events.LINE, events.PY_RETURN])
136136
}
137137

138-
fn on_py_start(&mut self, py: Python<'_>, code: &CodeObjectWrapper, _offset: i32) {
138+
fn on_py_start(&mut self, py: Python<'_>, code: &CodeObjectWrapper, _offset: i32) -> PyResult<()> {
139139
// Activate lazily if configured; ignore until then
140140
self.ensure_started(py, code);
141-
if !self.started { return; }
141+
if !self.started { return Ok(()); }
142142
// Trace event entry
143143
match (code.filename(py), code.qualname(py)) {
144144
(Ok(fname), Ok(qname)) => {
@@ -148,7 +148,7 @@ impl Tracer for RuntimeTracer {
148148
}
149149
if let Ok(fid) = self.ensure_function_id(py, code) {
150150
// Attempt to capture function arguments from the current frame.
151-
// Fall back to empty args on any error to keep tracing robust.
151+
// Fail fast on any error per source-code rules.
152152
let mut args: Vec<runtime_tracing::FullValueRecord> = Vec::new();
153153
let frame_and_args = (|| -> PyResult<()> {
154154
// Current Python frame where the function just started executing
@@ -226,11 +226,18 @@ impl Tracer for RuntimeTracer {
226226
Ok(())
227227
})();
228228
if let Err(e) = frame_and_args {
229-
log::debug!("[RuntimeTracer] on_py_start: failed to capture args: {}", e);
229+
// Raise a clear error; do not silently continue with empty args.
230+
let rete =Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
231+
"on_py_start: failed to capture args: {}",
232+
e
233+
)));
234+
log::debug!("error {:?}", rete);
235+
return rete;
230236
}
231237

232238
TraceWriter::register_call(&mut self.writer, fid, args);
233239
}
240+
Ok(())
234241
}
235242

236243
fn on_line(&mut self, py: Python<'_>, code: &CodeObjectWrapper, lineno: u32) {

codetracer-python-recorder/src/tracer.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,11 @@ pub trait Tracer: Send + Any {
190190
}
191191

192192
/// Called at start of a Python function (frame on stack).
193-
fn on_py_start(&mut self, _py: Python<'_>, _code: &CodeObjectWrapper, _offset: i32) {}
193+
///
194+
/// Implementations should fail fast on irrecoverable conditions
195+
/// (e.g., inability to access the current frame/locals) by
196+
/// returning an error.
197+
fn on_py_start(&mut self, _py: Python<'_>, _code: &CodeObjectWrapper, _offset: i32) -> PyResult<()> { Ok(()) }
194198

195199
/// Called on resumption of a generator/coroutine (not via throw()).
196200
fn on_py_resume(&mut self, _py: Python<'_>, _code: &CodeObjectWrapper, _offset: i32) {}
@@ -557,7 +561,7 @@ fn callback_branch(
557561
fn callback_py_start(py: Python<'_>, code: Bound<'_, PyCode>, instruction_offset: i32) -> PyResult<()> {
558562
if let Some(global) = GLOBAL.lock().unwrap().as_mut() {
559563
let wrapper = global.registry.get_or_insert(py, &code);
560-
global.tracer.on_py_start(py, &wrapper, instruction_offset);
564+
return global.tracer.on_py_start(py, &wrapper, instruction_offset);
561565
}
562566
Ok(())
563567
}

codetracer-python-recorder/tests/print_tracer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,12 @@ impl Tracer for CountingTracer {
107107
}
108108
}
109109

110-
fn on_py_start(&mut self, py: Python<'_>, code: &CodeObjectWrapper, offset: i32) {
110+
fn on_py_start(&mut self, py: Python<'_>, code: &CodeObjectWrapper, offset: i32) -> PyResult<()> {
111111
PY_START_COUNT.fetch_add(1, Ordering::SeqCst);
112112
if let Ok(Some(line)) = code.line_for_offset(py, offset as u32) {
113113
println!("PY_START at {}", line);
114114
}
115+
Ok(())
115116
}
116117

117118
fn on_py_resume(&mut self, py: Python<'_>, code: &CodeObjectWrapper, offset: i32) {

codetracer-python-recorder/tests/test_fail_fast_on_py_start.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
def test_fail_fast_when_frame_access_fails(tmp_path: Path):
99
# Import the built extension module
10-
import codetracer_python_recorder as cpr
10+
import codetracer_python_recorder.codetracer_python_recorder as cpr
1111

1212
# Prepare a simple program that triggers a Python function call
1313
prog = tmp_path / "prog.py"

issues.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ encountering such edge cases.
100100
- Tests verify the fail-fast path.
101101

102102
### Status
103-
Not started
103+
Done
104+
105+
`RuntimeTracer::on_py_start` now returns `PyResult<()>` and raises a
106+
`RuntimeError` when frame/locals access fails; `callback_py_start` propagates
107+
the error to Python. A pytest (`tests/test_fail_fast_on_py_start.py`) asserts
108+
the fail-fast behavior by monkeypatching `sys._getframe` to raise.
104109

105110
## ISSUE-004
106111
### Description
@@ -118,15 +123,3 @@ when `Raw` is expected) and update tests to assert the exact kind.
118123
Done
119124

120125
Stricter tests now assert `str` values are encoded as `String` with the exact text payload, and runtime docs clarify canonical encoding. No runtime logic change was required since `encode_value` already produced `String` for Python `str`.
121-
122-
## ISSUE-006
123-
### Description
124-
Accidental check-in of Cargo cache/artifact files under `codetracer-python-recorder/.cargo/**` (e.g., `registry/CACHEDIR.TAG`, `.package-cache`). These are build/cache directories and should be excluded from version control.
125-
126-
### Definition of Done
127-
- Add ignore rules to exclude Cargo cache directories (e.g., `.cargo/**`, `target/**`) from version control.
128-
- Remove already-checked-in cache files from the repository.
129-
- Verify the working tree is clean after a clean build; no cache artifacts appear as changes.
130-
131-
### Status
132-
Archived

0 commit comments

Comments
 (0)