Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Do not change with_recursion as this has side effects
  • Loading branch information
bschoenmaeckers committed Oct 1, 2026
commit 5eb882cddee62d0fdc44923742a56712f135a1da
34 changes: 24 additions & 10 deletions crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2586,15 +2586,7 @@ impl VirtualMachine {
}

/// Enter a native-recursion section equivalent to `Py_EnterRecursiveCall`.
///
/// This bounds native recursion that pushes no Python frame, against the
/// native stack. That is a separate budget from the frame limit
/// `sys.setrecursionlimit()` sets, so nesting counted here does not come
/// out of what Python code has left to call with.
pub fn enter_recursive_call(&self, _where: &str) -> PyResult<()> {
// `check_c_stack_overflow()` answers no unconditionally where the stack
// pointer cannot be read, which would leave this guard with nothing to
// stop. A count of the nesting stands in for the measurement there.
#[cfg(any(miri, target_env = "musl"))]
let counted_too_deep =
self.native_recursion_depth.get() >= Self::NATIVE_RECURSION_LIMIT_UNMEASURED;
Expand Down Expand Up @@ -2623,11 +2615,33 @@ impl VirtualMachine {
/// Used to run the body of a (possibly) recursive function. It will raise a
/// RecursionError if recursive functions are nested far too many times,
/// preventing a stack overflow.
/// `Py_EnterRecursiveCall`: bounds native recursion that pushes no Python
/// frame, against the native stack. That is a separate budget from the
/// frame limit `sys.setrecursionlimit()` sets, so nesting counted here does
/// not come out of what Python code has left to call with.
pub fn with_recursion<R, F: FnOnce() -> PyResult<R>>(&self, _where: &str, f: F) -> PyResult<R> {
self.enter_recursive_call(_where)?;
// `check_c_stack_overflow()` answers no unconditionally where the stack
// pointer cannot be read, which would leave this guard with nothing to
// stop. A count of the nesting stands in for the measurement there.
#[cfg(any(miri, target_env = "musl"))]
let counted_too_deep =
self.native_recursion_depth.get() >= Self::NATIVE_RECURSION_LIMIT_UNMEASURED;
#[cfg(not(any(miri, target_env = "musl")))]
let counted_too_deep = false;

if counted_too_deep || self.check_c_stack_overflow() {
return Err(
self.new_recursion_error(format!("maximum recursion depth exceeded {_where}"))
);
}

#[cfg(any(miri, target_env = "musl"))]
let _native_depth_guard = scopeguard::guard((), |()| self.leave_recursive_call());
let _native_depth_guard = {
self.native_recursion_depth.update(|d| d + 1);
scopeguard::guard((), |()| {
self.native_recursion_depth.update(|d| d.saturating_sub(1))
})
};

f()
}
Expand Down
Loading