xgrammar/support/
recursion_guard.rs1use std::{
9 cell::Cell,
10 sync::atomic::{AtomicI32, Ordering},
11};
12
13pub const DEFAULT_MAX_RECURSION_DEPTH: i32 = 10_000;
15
16pub const MAX_REASONABLE_RECURSION_DEPTH: i32 = 1_000_000;
18
19const ENV_VAR: &str = "XGRAMMAR_MAX_RECURSION_DEPTH";
20
21static MAX_RECURSION_DEPTH: AtomicI32 = AtomicI32::new(0);
23
24thread_local! {
25 static CURRENT_DEPTH: Cell<i32> = const { Cell::new(0) };
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
31pub enum RecursionError {
32 #[error(
34 "RecursionGuard: Maximum recursion depth must be positive and less than {MAX_REASONABLE_RECURSION_DEPTH}, \
35 got: {0}"
36 )]
37 InvalidMax(i32),
38 #[error("RecursionGuard: Maximum recursion depth exceeded. Current depth: {current}, Max allowed: {max}")]
40 DepthExceeded {
41 current: i32,
43 max: i32,
45 },
46}
47
48fn load_max_from_env() -> i32 {
49 match std::env::var(ENV_VAR).ok().and_then(|v| v.parse::<i32>().ok()) {
50 Some(v) if v > 0 && v <= MAX_REASONABLE_RECURSION_DEPTH => v,
51 _ => DEFAULT_MAX_RECURSION_DEPTH,
52 }
53}
54
55#[must_use]
58pub fn get_max_recursion_depth() -> i32 {
59 let current = MAX_RECURSION_DEPTH.load(Ordering::Relaxed);
60 if current != 0 {
61 return current;
62 }
63 let initial = load_max_from_env();
64 MAX_RECURSION_DEPTH.store(initial, Ordering::Relaxed);
65 initial
66}
67
68pub fn set_max_recursion_depth(max_depth: i32) -> Result<(), RecursionError> {
74 if max_depth <= 0 || max_depth > MAX_REASONABLE_RECURSION_DEPTH {
75 return Err(RecursionError::InvalidMax(max_depth));
76 }
77 MAX_RECURSION_DEPTH.store(max_depth, Ordering::Relaxed);
78 Ok(())
79}
80
81pub fn reset_recursion_depth() {
83 CURRENT_DEPTH.with(|d| d.set(0));
84}
85
86#[derive(Debug)]
89#[must_use = "the guard must be held for the duration of the recursive call"]
90pub struct RecursionGuard {
91 _private: (),
93}
94
95impl RecursionGuard {
96 pub fn enter() -> Result<Self, RecursionError> {
102 let max = get_max_recursion_depth();
103 let current = CURRENT_DEPTH.with(|d| {
104 let next = d.get() + 1;
105 d.set(next);
106 next
107 });
108 if current > max {
109 CURRENT_DEPTH.with(|d| d.set(d.get() - 1));
110 return Err(RecursionError::DepthExceeded {
111 current,
112 max,
113 });
114 }
115 Ok(Self {
116 _private: (),
117 })
118 }
119}
120
121impl Drop for RecursionGuard {
122 fn drop(&mut self) {
123 CURRENT_DEPTH.with(|d| d.set(d.get() - 1));
124 }
125}