latexml_core/stack_guard.rs
1//! Configurable native-stack growth guard for deeply-recursive digestion.
2//!
3//! Some inputs recurse through the engine far deeper than a normal document:
4//! gullet macro expansion (a number-argument macro whose argument is read by
5//! expanding the next number-argument macro — xint's `\XINT_…` chains nest tens
6//! of thousands deep), the document tree walk, and the math-tree walk. Left
7//! unguarded these overflow the (large but finite) conversion-thread stack and
8//! **abort the process** (SIGABRT) — whereas Perl degrades gracefully via its
9//! `$MAXSTACK` guard. Each such site therefore grows the native stack on demand
10//! with [`stacker::maybe_grow`].
11//!
12//! This module is the single home for the two parameters those calls share, so
13//! they are tuned in **one** place and are **configurable at runtime** rather
14//! than hardcoded:
15//! - **red zone** — grow once fewer than this many bytes of stack remain.
16//! - **segment** — the size of each freshly-allocated stack chunk.
17//!
18//! Resolution precedence (highest first): an explicit [`set_red_zone_bytes`] /
19//! [`set_segment_bytes`] (e.g. from a future `--stack-…` CLI flag) → the env
20//! var ([`ENV_RED_ZONE`] / [`ENV_SEGMENT`], a plain byte count) → the compiled
21//! default. Call [`maybe_grow`] at every deeply-recursive site instead of
22//! `stacker::maybe_grow` directly.
23
24use std::sync::atomic::{AtomicUsize, Ordering};
25
26/// Default red zone: grow when within this many bytes of the stack end.
27/// 256 KiB leaves ample margin above any single recursion frame.
28pub const DEFAULT_RED_ZONE_BYTES: usize = 256 * 1024;
29
30/// Default growth segment: bytes of fresh stack allocated per growth step.
31/// 8 MiB amortizes the allocation across many recursion levels.
32pub const DEFAULT_SEGMENT_BYTES: usize = 8 * 1024 * 1024;
33
34/// Env override for the red zone — a plain byte count (e.g. `262144`).
35pub const ENV_RED_ZONE: &str = "LATEXML_STACK_RED_ZONE_BYTES";
36
37/// Env override for the growth segment — a plain byte count (e.g. `8388608`).
38pub const ENV_SEGMENT: &str = "LATEXML_STACK_SEGMENT_BYTES";
39
40// 0 is the "unresolved" sentinel: the value is resolved from env-or-default on
41// first read and cached. `set_*` stores a non-zero override that wins thereafter.
42static RED_ZONE: AtomicUsize = AtomicUsize::new(0);
43static SEGMENT: AtomicUsize = AtomicUsize::new(0);
44
45fn resolve(slot: &AtomicUsize, env_key: &str, default: usize) -> usize {
46 match slot.load(Ordering::Relaxed) {
47 0 => {
48 let value = std::env::var(env_key)
49 .ok()
50 .and_then(|s| s.trim().parse::<usize>().ok())
51 .filter(|&v| v != 0)
52 .unwrap_or(default);
53 // Benign race: concurrent first-readers resolve to the identical value.
54 slot.store(value, Ordering::Relaxed);
55 value
56 },
57 value => value,
58 }
59}
60
61/// Bytes of remaining stack below which [`maybe_grow`] allocates a new segment.
62#[inline]
63pub fn red_zone_bytes() -> usize { resolve(&RED_ZONE, ENV_RED_ZONE, DEFAULT_RED_ZONE_BYTES) }
64
65/// Size, in bytes, of each freshly-allocated stack segment.
66#[inline]
67pub fn segment_bytes() -> usize { resolve(&SEGMENT, ENV_SEGMENT, DEFAULT_SEGMENT_BYTES) }
68
69/// Override the red zone (e.g. from a CLI flag). Set before any conversion;
70/// takes precedence over the env var and the default.
71pub fn set_red_zone_bytes(bytes: usize) { RED_ZONE.store(bytes.max(1), Ordering::Relaxed); }
72
73/// Override the growth segment (e.g. from a CLI flag). Set before any
74/// conversion; takes precedence over the env var and the default.
75pub fn set_segment_bytes(bytes: usize) { SEGMENT.store(bytes.max(1), Ordering::Relaxed); }
76
77/// Grow the native call stack on demand, then run `f`. The single wrapper every
78/// deeply-recursive site should call so the guard parameters live in one place.
79/// Transparent: it only provides more stack when near the limit; it never
80/// changes results.
81#[inline]
82pub fn maybe_grow<R>(f: impl FnOnce() -> R) -> R {
83 stacker::maybe_grow(red_zone_bytes(), segment_bytes(), f)
84}