X Tutup
Skip to content

Commit f28dd63

Browse files
committed
Match monitoring DISABLE and error handling to CPython behavior
- Remove ERROR_PROPAGATING thread-local: callback errors now propagate normally without suppressing all future events - Change DISABLE granularity from (code, tool, event) to (code, offset, tool) matching CPython's per-instruction remove_tools() - Add offset parameter to fire_event_inner and fire_line - Skip test_highly_nested_objects_encoding (timeout, not failure)
1 parent 3051275 commit f28dd63

File tree

12 files changed

+205
-158
lines changed

12 files changed

+205
-158
lines changed

Lib/test/test_json/test_recursion.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ def test_highly_nested_objects_decoding(self):
9292
@support.skip_wasi_stack_overflow()
9393
@support.skip_emscripten_stack_overflow()
9494
@support.requires_resource('cpu')
95-
# TODO: RUSTPYTHON; timeout with 500k depth recursion
96-
@unittest.expectedFailure
9795
def test_highly_nested_objects_encoding(self):
9896
# See #12051
9997
l, d = [], {}

crates/stdlib/src/faulthandler.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ mod decl {
325325

326326
/// Write a frame's info to an fd using signal-safe I/O.
327327
#[cfg(any(unix, windows))]
328-
fn dump_frame_from_ref(fd: i32, frame: &crate::vm::PyRef<Frame>) {
328+
fn dump_frame_from_ref(fd: i32, frame: &crate::vm::Py<Frame>) {
329329
let funcname = frame.code.obj_name.as_str();
330330
let filename = frame.code.source_path().as_str();
331331
let lineno = if frame.lasti() == 0 {
@@ -381,8 +381,9 @@ mod decl {
381381
} else {
382382
puts(fd, "Stack (most recent call first):\n");
383383
let frames = vm.frames.borrow();
384-
for frame in frames.iter().rev() {
385-
dump_frame_from_ref(fd, frame);
384+
for fp in frames.iter().rev() {
385+
// SAFETY: the frame is alive while it's in the Vec
386+
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
386387
}
387388
}
388389
}
@@ -420,8 +421,8 @@ mod decl {
420421
if frames.is_empty() {
421422
puts(fd, " <no Python frame>\n");
422423
} else {
423-
for frame in frames.iter().rev() {
424-
dump_frame_from_ref(fd, frame);
424+
for fp in frames.iter().rev() {
425+
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
425426
}
426427
}
427428
}
@@ -430,8 +431,8 @@ mod decl {
430431
{
431432
write_thread_id(fd, current_thread_id(), true);
432433
let frames = vm.frames.borrow();
433-
for frame in frames.iter().rev() {
434-
dump_frame_from_ref(fd, frame);
434+
for fp in frames.iter().rev() {
435+
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
435436
}
436437
}
437438
}

crates/vm/src/builtins/frame.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,13 @@ impl Py<Frame> {
204204
.frames
205205
.borrow()
206206
.iter()
207-
.find(|f| {
208-
let ptr: *const Frame = &****f;
207+
.find(|fp| {
208+
// SAFETY: the caller keeps the FrameRef alive while it's in the Vec
209+
let py: &crate::Py<Frame> = unsafe { fp.as_ref() };
210+
let ptr: *const Frame = &**py;
209211
core::ptr::eq(ptr, previous)
210212
})
211-
.cloned()
213+
.map(|fp| unsafe { fp.as_ref() }.to_owned())
212214
{
213215
return Some(frame);
214216
}

crates/vm/src/coroutine.rs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::{
22
AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
3-
builtins::{PyBaseExceptionRef, PyStrRef},
3+
builtins::PyStrRef,
44
common::lock::PyMutex,
55
exceptions::types::PyBaseException,
66
frame::{ExecutionResult, FrameOwner, FrameRef},
77
function::OptionalArg,
8-
object::{Traverse, TraverseFn},
8+
object::{PyAtomicRef, Traverse, TraverseFn},
99
protocol::PyIterReturn,
1010
};
1111
use crossbeam_utils::atomic::AtomicCell;
@@ -36,15 +36,17 @@ pub struct Coro {
3636
// _weakreflist
3737
name: PyMutex<PyStrRef>,
3838
qualname: PyMutex<PyStrRef>,
39-
exception: PyMutex<Option<PyBaseExceptionRef>>, // exc_state
39+
exception: PyAtomicRef<Option<PyBaseException>>, // exc_state
4040
}
4141

4242
unsafe impl Traverse for Coro {
4343
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
4444
self.frame.traverse(tracer_fn);
4545
self.name.traverse(tracer_fn);
4646
self.qualname.traverse(tracer_fn);
47-
self.exception.traverse(tracer_fn);
47+
if let Some(exc) = self.exception.deref() {
48+
exc.traverse(tracer_fn);
49+
}
4850
}
4951
}
5052

@@ -65,7 +67,7 @@ impl Coro {
6567
frame,
6668
closed: AtomicCell::new(false),
6769
running: AtomicCell::new(false),
68-
exception: PyMutex::default(),
70+
exception: PyAtomicRef::from(None),
6971
name: PyMutex::new(name),
7072
qualname: PyMutex::new(qualname),
7173
}
@@ -92,33 +94,20 @@ impl Coro {
9294
func: F,
9395
) -> PyResult<ExecutionResult>
9496
where
95-
F: FnOnce(FrameRef) -> PyResult<ExecutionResult>,
97+
F: FnOnce(&FrameRef) -> PyResult<ExecutionResult>,
9698
{
9799
if self.running.compare_exchange(false, true).is_err() {
98100
return Err(vm.new_value_error(format!("{} already executing", gen_name(jen, vm))));
99101
}
100102

101-
// swap exception state
102-
// Get generator's saved exception state from last yield
103-
let gen_exc = self.exception.lock().take();
104-
105-
// Use a slot to capture generator's exception state before with_frame pops
106-
let exception_slot = &self.exception;
103+
// SAFETY: running.compare_exchange guarantees exclusive access
104+
let gen_exc = unsafe { self.exception.swap(None) };
105+
let exception_ptr = &self.exception as *const PyAtomicRef<Option<PyBaseException>>;
107106

108-
// Run the generator frame
109-
// with_frame does push_exception(None) which creates a new exception context
110-
// The caller's exception remains in the chain via prev, so topmost_exception()
111-
// will find it if generator's exception is None
112-
let result = vm.with_frame(self.frame.clone(), |f| {
113-
// with_frame pushed None, creating: { exc: None, prev: caller's exc_info }
114-
// Pop None and push generator's exception instead
115-
// This maintains the chain: { exc: gen_exc, prev: caller's exc_info }
116-
vm.pop_exception();
117-
vm.push_exception(gen_exc);
107+
let result = vm.resume_gen_frame(&self.frame, gen_exc, |f| {
118108
let result = func(f);
119-
// Save generator's exception state BEFORE with_frame pops
120-
// This is the generator's current exception context
121-
*exception_slot.lock() = vm.current_exception();
109+
// SAFETY: exclusive access guaranteed by running flag
110+
let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) };
122111
result
123112
});
124113

crates/vm/src/frame.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,11 @@ impl ExecutingFrame<'_> {
490490
let monitoring_mask = vm.state.monitoring_events.load();
491491
if monitoring_mask != 0 {
492492
use crate::stdlib::sys::monitoring;
493+
let offset = idx as u32 * 2;
493494
if monitoring_mask & monitoring::EVENT_LINE != 0 && line != prev_line {
494-
monitoring::fire_line(vm, self.code, line)?;
495+
monitoring::fire_line(vm, self.code, offset, line)?;
495496
}
496497
if monitoring_mask & monitoring::EVENT_INSTRUCTION != 0 {
497-
let offset = idx as u32 * 2;
498498
monitoring::fire_instruction(vm, self.code, offset)?;
499499
}
500500
}

crates/vm/src/protocol/callable.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
builtins::{PyBoundMethod, PyFunction},
33
function::{FuncArgs, IntoFuncArgs},
44
types::GenericMethod,
5-
{AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine},
5+
{PyObject, PyObjectRef, PyResult, VirtualMachine},
66
};
77

88
impl PyObject {
@@ -111,12 +111,11 @@ impl VirtualMachine {
111111
return Ok(());
112112
}
113113

114-
let frame_ref = self.current_frame();
115-
if frame_ref.is_none() {
114+
let Some(frame_ref) = self.current_frame() else {
116115
return Ok(());
117-
}
116+
};
118117

119-
let frame = frame_ref.unwrap().as_object().to_owned();
118+
let frame: PyObjectRef = frame_ref.into();
120119
let event = self.ctx.new_str(event.to_string()).into();
121120
let args = vec![frame, event, arg.unwrap_or_else(|| self.ctx.none())];
122121

crates/vm/src/stdlib/builtins.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ mod builtins {
384384
)
385385
}
386386
None => (
387-
vm.current_globals().clone(),
387+
vm.current_globals(),
388388
if let Some(locals) = self.locals {
389389
locals
390390
} else {
@@ -503,7 +503,7 @@ mod builtins {
503503

504504
#[pyfunction]
505505
fn globals(vm: &VirtualMachine) -> PyDictRef {
506-
vm.current_globals().clone()
506+
vm.current_globals()
507507
}
508508

509509
#[pyfunction]

crates/vm/src/stdlib/sys/mod.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -998,34 +998,34 @@ mod sys {
998998
#[pyfunction]
999999
fn _getframe(offset: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult<FrameRef> {
10001000
let offset = offset.into_option().unwrap_or(0);
1001-
if offset > vm.frames.borrow().len() - 1 {
1001+
let frames = vm.frames.borrow();
1002+
let len = frames.len();
1003+
if offset >= len {
10021004
return Err(vm.new_value_error("call stack is not deep enough"));
10031005
}
1004-
let idx = vm.frames.borrow().len() - offset - 1;
1005-
let frame = &vm.frames.borrow()[idx];
1006-
Ok(frame.clone())
1006+
// SAFETY: the caller keeps the FrameRef alive while it's in the Vec
1007+
let frame = unsafe { frames[len - offset - 1].as_ref() };
1008+
Ok(frame.to_owned())
10071009
}
10081010

10091011
#[pyfunction]
10101012
fn _getframemodulename(depth: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult {
10111013
let depth = depth.into_option().unwrap_or(0);
10121014

1013-
// Get the frame at the specified depth
1014-
if depth > vm.frames.borrow().len() - 1 {
1015+
let frames = vm.frames.borrow();
1016+
let len = frames.len();
1017+
if depth >= len {
10151018
return Ok(vm.ctx.none());
10161019
}
10171020

1018-
let idx = vm.frames.borrow().len() - depth - 1;
1019-
let frame = &vm.frames.borrow()[idx];
1021+
// SAFETY: the caller keeps the FrameRef alive while it's in the Vec
1022+
let frame = unsafe { frames[len - depth - 1].as_ref() };
10201023

10211024
// If the frame has a function object, return its __module__ attribute
10221025
if let Some(func_obj) = &frame.func_obj {
10231026
match func_obj.get_attr(identifier!(vm, __module__), vm) {
10241027
Ok(module) => Ok(module),
1025-
Err(_) => {
1026-
// CPython clears the error and returns None
1027-
Ok(vm.ctx.none())
1028-
}
1028+
Err(_) => Ok(vm.ctx.none()),
10291029
}
10301030
} else {
10311031
Ok(vm.ctx.none())

0 commit comments

Comments
 (0)
X Tutup