Skip to content

Commit ed5be25

Browse files
committed
remove framesnapshot
1 parent dc6c6ef commit ed5be25

File tree

6 files changed

+95
-189
lines changed

6 files changed

+95
-189
lines changed

crates/stdlib/src/faulthandler.rs

Lines changed: 58 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ mod decl {
66
use crate::vm::{
77
PyObjectRef, PyResult, VirtualMachine,
88
frame::Frame,
9-
frame_snapshot::SNAPSHOT_ENABLED,
109
function::{ArgIntoFloat, OptionalArg},
1110
};
1211
use alloc::sync::Arc;
@@ -228,41 +227,59 @@ mod decl {
228227
puts(fd, " (most recent call first):\n");
229228
}
230229

231-
// dump_frame (traceback.c:1037-1087)
230+
/// Dump the current thread's live frame chain to fd (signal-safe).
231+
/// Walks the `Frame.previous` pointer chain starting from the
232+
/// thread-local current frame pointer.
232233
#[cfg(any(unix, windows))]
233-
fn dump_frame(fd: i32, filename: &[u8], lineno: u32, funcname: &[u8]) {
234-
puts(fd, " File \"");
235-
puts_bytes(fd, filename);
236-
puts(fd, "\", line ");
237-
dump_decimal(fd, lineno as usize);
238-
puts(fd, " in ");
239-
puts_bytes(fd, funcname);
240-
puts(fd, "\n");
234+
fn dump_live_frames(fd: i32) {
235+
const MAX_FRAME_DEPTH: usize = 100;
236+
237+
let mut frame_ptr = crate::vm::vm::thread::get_current_frame();
238+
if frame_ptr.is_null() {
239+
puts(fd, " <no Python frame>\n");
240+
return;
241+
}
242+
let mut depth = 0;
243+
while !frame_ptr.is_null() && depth < MAX_FRAME_DEPTH {
244+
let frame = unsafe { &*frame_ptr };
245+
dump_frame_from_raw(fd, frame);
246+
frame_ptr = frame.previous_frame();
247+
depth += 1;
248+
}
249+
if depth >= MAX_FRAME_DEPTH && !frame_ptr.is_null() {
250+
puts(fd, " ...\n");
251+
}
241252
}
242253

243-
/// Dump frame snapshots to fd (signal-safe)
254+
/// Dump a single frame's info to fd (signal-safe), reading live data.
244255
#[cfg(any(unix, windows))]
245-
fn dump_snapshot_frames(fd: i32) {
246-
use crate::vm::frame_snapshot::{FRAME_SNAPSHOTS, SNAPSHOT_COUNT};
247-
let count = SNAPSHOT_COUNT.load(Ordering::Acquire);
248-
if count > 0 {
249-
#[allow(clippy::needless_range_loop)]
250-
for i in 0..count {
251-
unsafe {
252-
let snap = &FRAME_SNAPSHOTS[i];
253-
if snap.filename_len > 0 {
254-
dump_frame(
255-
fd,
256-
&snap.filename[..snap.filename_len],
257-
snap.lineno,
258-
&snap.funcname[..snap.funcname_len],
259-
);
260-
}
261-
}
256+
fn dump_frame_from_raw(fd: i32, frame: &Frame) {
257+
let filename = frame.code.source_path.as_str();
258+
let funcname = frame.code.obj_name.as_str();
259+
let lasti = frame.lasti();
260+
let lineno = if lasti == 0 {
261+
frame.code.first_line_number.map(|n| n.get()).unwrap_or(1) as u32
262+
} else {
263+
let idx = (lasti as usize).saturating_sub(1);
264+
if idx < frame.code.locations.len() {
265+
frame.code.locations[idx].0.line.get() as u32
266+
} else {
267+
frame.code.first_line_number.map(|n| n.get()).unwrap_or(0) as u32
262268
}
269+
};
270+
271+
puts(fd, " File \"");
272+
puts(fd, filename);
273+
puts(fd, "\", line ");
274+
dump_decimal(fd, lineno as usize);
275+
puts(fd, " in ");
276+
if funcname.len() > MAX_FUNCTION_NAME_LEN {
277+
puts(fd, &funcname[..MAX_FUNCTION_NAME_LEN]);
278+
puts(fd, "...");
263279
} else {
264-
puts(fd, " <no Python frame>\n");
280+
puts(fd, funcname);
265281
}
282+
puts(fd, "\n");
266283
}
267284

268285
// faulthandler_dump_traceback (signal-safe, for fatal errors)
@@ -281,16 +298,11 @@ mod decl {
281298
puts(fd, "Stack (most recent call first):\n");
282299
}
283300

284-
dump_snapshot_frames(fd);
301+
dump_live_frames(fd);
285302

286303
REENTRANT.store(false, Ordering::SeqCst);
287304
}
288305

289-
/// Snapshot current Python frames into signal-safe global storage.
290-
fn snapshot_current_frames(vm: &VirtualMachine) {
291-
crate::vm::frame_snapshot::do_update_frame_snapshots(&vm.frames.borrow());
292-
}
293-
294306
const MAX_FUNCTION_NAME_LEN: usize = 500;
295307

296308
/// Write a frame's info to an fd using signal-safe I/O.
@@ -444,10 +456,6 @@ mod decl {
444456
.all_threads
445457
.store(args.all_threads, Ordering::Relaxed);
446458

447-
// Enable frame snapshot updates and immediately snapshot existing frames
448-
SNAPSHOT_ENABLED.store(true, Ordering::Relaxed);
449-
snapshot_current_frames(vm);
450-
451459
// Install signal handlers
452460
if !faulthandler_enable_internal() {
453461
return Err(vm.new_runtime_error("Failed to enable faulthandler".to_owned()));
@@ -753,29 +761,11 @@ mod decl {
753761
FATAL_ERROR.enabled.store(false, Ordering::Relaxed);
754762
}
755763

756-
// Check if any faulthandler feature needs snapshots
757-
#[cfg(unix)]
758-
fn any_user_signal_enabled() -> bool {
759-
for signum in 1..64 {
760-
if user_signals::is_enabled(signum) {
761-
return true;
762-
}
763-
}
764-
false
765-
}
766-
767764
// faulthandler_disable_py
768765
#[pyfunction]
769766
fn disable() -> bool {
770767
let was_enabled = FATAL_ERROR.enabled.load(Ordering::Relaxed);
771768
faulthandler_disable_internal();
772-
// Disable snapshots only if no user signals are registered
773-
#[cfg(unix)]
774-
if !any_user_signal_enabled() {
775-
SNAPSHOT_ENABLED.store(false, Ordering::Relaxed);
776-
}
777-
#[cfg(not(unix))]
778-
SNAPSHOT_ENABLED.store(false, Ordering::Relaxed);
779769
was_enabled
780770
}
781771

@@ -874,7 +864,7 @@ mod decl {
874864
puts_bytes(fd, header.as_bytes());
875865

876866
// Use thread frame slots when threading is enabled (includes all threads).
877-
// Fall back to frame snapshots for non-threaded builds.
867+
// Fall back to live frame walking for non-threaded builds.
878868
#[cfg(feature = "threading")]
879869
{
880870
for (tid, slot) in &thread_frame_slots {
@@ -885,7 +875,7 @@ mod decl {
885875
#[cfg(not(feature = "threading"))]
886876
{
887877
write_thread_id(fd, false);
888-
dump_snapshot_frames(fd);
878+
dump_live_frames(fd);
889879
}
890880

891881
if exit {
@@ -930,10 +920,6 @@ mod decl {
930920

931921
let header = format_timeout(timeout_us);
932922

933-
// Enable frame snapshot updates so watchdog can read them
934-
SNAPSHOT_ENABLED.store(true, Ordering::Relaxed);
935-
snapshot_current_frames(vm);
936-
937923
// Snapshot thread frame slots so watchdog can dump tracebacks
938924
#[cfg(feature = "threading")]
939925
let thread_frame_slots: Vec<(u64, ThreadFrameSlot)> = {
@@ -1076,8 +1062,8 @@ mod decl {
10761062
puts(user.fd, "Stack (most recent call first):\n");
10771063
}
10781064

1079-
// Dump frame snapshots (updated by main thread on every frame push/pop)
1080-
dump_snapshot_frames(user.fd);
1065+
// Dump live frame chain (walks Frame.previous pointers)
1066+
dump_live_frames(user.fd);
10811067

10821068
// If chain is enabled, call the previous handler
10831069
if user.chain {
@@ -1140,10 +1126,6 @@ mod decl {
11401126

11411127
let signum = args.signum as usize;
11421128

1143-
// Enable frame snapshot updates and immediately snapshot existing frames
1144-
SNAPSHOT_ENABLED.store(true, Ordering::Relaxed);
1145-
snapshot_current_frames(vm);
1146-
11471129
// Get current handler to save as previous
11481130
let previous = if !user_signals::is_enabled(signum) {
11491131
// Install signal handler using sigaction with SA_NODEFER
@@ -1201,11 +1183,11 @@ mod decl {
12011183
// Test functions for faulthandler testing
12021184

12031185
#[pyfunction]
1204-
fn _read_null(vm: &VirtualMachine) {
1186+
fn _read_null(_vm: &VirtualMachine) {
12051187
#[cfg(not(target_arch = "wasm32"))]
12061188
{
12071189
suppress_crash_report();
1208-
snapshot_current_frames(vm);
1190+
12091191
unsafe {
12101192
let ptr: *const i32 = core::ptr::null();
12111193
core::ptr::read_volatile(ptr);
@@ -1221,11 +1203,10 @@ mod decl {
12211203
}
12221204

12231205
#[pyfunction]
1224-
fn _sigsegv(_args: SigsegvArgs, vm: &VirtualMachine) {
1206+
fn _sigsegv(_args: SigsegvArgs, _vm: &VirtualMachine) {
12251207
#[cfg(not(target_arch = "wasm32"))]
12261208
{
12271209
suppress_crash_report();
1228-
snapshot_current_frames(vm);
12291210

12301211
// Write to NULL pointer to trigger a real hardware SIGSEGV,
12311212
// matching CPython's *((volatile int *)NULL) = 0;
@@ -1239,23 +1220,23 @@ mod decl {
12391220
}
12401221

12411222
#[pyfunction]
1242-
fn _sigabrt(vm: &VirtualMachine) {
1223+
fn _sigabrt(_vm: &VirtualMachine) {
12431224
#[cfg(not(target_arch = "wasm32"))]
12441225
{
12451226
suppress_crash_report();
1246-
snapshot_current_frames(vm);
1227+
12471228
unsafe {
12481229
libc::abort();
12491230
}
12501231
}
12511232
}
12521233

12531234
#[pyfunction]
1254-
fn _sigfpe(vm: &VirtualMachine) {
1235+
fn _sigfpe(_vm: &VirtualMachine) {
12551236
#[cfg(not(target_arch = "wasm32"))]
12561237
{
12571238
suppress_crash_report();
1258-
snapshot_current_frames(vm);
1239+
12591240
unsafe {
12601241
libc::raise(libc::SIGFPE);
12611242
}

crates/vm/src/frame.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use crate::{
2525
};
2626
use alloc::fmt;
2727
use core::iter::zip;
28-
#[cfg(feature = "threading")]
2928
use core::sync::atomic;
29+
use core::sync::atomic::AtomicPtr;
3030
use indexmap::IndexMap;
3131
use itertools::Itertools;
3232

@@ -90,6 +90,9 @@ pub struct Frame {
9090
/// Borrowed reference (not ref-counted) to avoid Generator↔Frame cycle.
9191
/// Cleared by the generator's Drop impl.
9292
pub generator: PyAtomicBorrow,
93+
/// Previous frame in the call chain for signal-safe traceback walking.
94+
/// Mirrors `_PyInterpreterFrame.previous`.
95+
pub(crate) previous: AtomicPtr<Frame>,
9396
}
9497

9598
impl PyPayload for Frame {
@@ -179,6 +182,7 @@ impl Frame {
179182
trace_opcodes: PyMutex::new(false),
180183
temporary_refs: PyMutex::new(vec![]),
181184
generator: PyAtomicBorrow::new(),
185+
previous: AtomicPtr::new(core::ptr::null_mut()),
182186
}
183187
}
184188

@@ -197,6 +201,11 @@ impl Frame {
197201
self.code.locations[self.lasti() as usize - 1].0
198202
}
199203

204+
/// Get the previous frame pointer for signal-safe traceback walking.
205+
pub fn previous_frame(&self) -> *const Frame {
206+
self.previous.load(atomic::Ordering::Relaxed)
207+
}
208+
200209
pub fn lasti(&self) -> u32 {
201210
#[cfg(feature = "threading")]
202211
{
@@ -398,12 +407,6 @@ impl ExecutingFrame<'_> {
398407
let mut arg_state = bytecode::OpArgState::default();
399408
loop {
400409
let idx = self.lasti() as usize;
401-
// Update frame snapshot line number for signal handlers (faulthandler)
402-
if idx < self.code.locations.len() {
403-
crate::frame_snapshot::update_current_lineno(
404-
self.code.locations[idx].0.line.get() as u32
405-
);
406-
}
407410
self.update_lasti(|i| *i += 1);
408411
let bytecode::CodeUnit { op, arg } = instructions[idx];
409412
let arg = arg_state.extend(arg);

0 commit comments

Comments
 (0)