use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use metrale_grammar::CompiledGrammar;
use super::PrewarmHook;
pub(super) type PrewarmGate = Arc<std::sync::Barrier>;
pub(super) enum Prewarm {
Pending {
handle: std::thread::JoinHandle<usize>,
finished: Arc<AtomicBool>,
},
Done(usize),
}
impl Prewarm {
pub(super) fn start(compiled: &CompiledGrammar, k: usize) -> Self {
Self::start_with(compiled, k, None, overlap_enabled(), None)
}
pub(super) fn start_with(
compiled: &CompiledGrammar,
k: usize,
gate: Option<PrewarmGate>,
overlap: bool,
on_warm: Option<PrewarmHook>,
) -> Self {
if !overlap {
drop(gate);
let warmed = compiled.compile_top_k_masks(k);
if let Some(hook) = on_warm {
hook(warmed);
}
return Prewarm::Done(warmed);
}
let owned = compiled.clone();
let finished = Arc::new(AtomicBool::new(false));
let done_flag = Arc::clone(&finished);
let spawned = std::thread::Builder::new()
.name("grammar-prewarm".to_string())
.spawn(move || {
if let Some(gate) = gate {
gate.wait();
}
let warmed = owned.compile_top_k_masks(k);
if let Some(hook) = on_warm {
hook(warmed);
}
done_flag.store(true, Ordering::Release);
warmed
});
match spawned {
Ok(handle) => Prewarm::Pending { handle, finished },
Err(e) => {
tracing::debug!("Grammar: prewarm thread spawn failed ({e}); warming inline");
Prewarm::Done(compiled.compile_top_k_masks(k))
}
}
}
pub(super) fn wait(&mut self) -> usize {
match self {
Prewarm::Done(n) => *n,
Prewarm::Pending { .. } => {
let Prewarm::Pending { handle, .. } = std::mem::replace(self, Prewarm::Done(0))
else {
unreachable!("just matched Pending");
};
let warmed = handle.join().unwrap_or_else(|_| {
tracing::warn!("Grammar: background mask prewarm panicked; masks stay lazy");
0
});
*self = Prewarm::Done(warmed);
warmed
}
}
}
#[cfg(test)]
pub(super) fn is_finished(&self) -> bool {
match self {
Prewarm::Done(_) => true,
Prewarm::Pending { finished, .. } => finished.load(Ordering::Acquire),
}
}
}
pub(super) fn overlap_enabled_for_serve() -> bool {
overlap_enabled()
}
fn overlap_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| {
overlap_from_env(
std::env::var("METRALE_GRAMMAR_ASYNC_PREWARM")
.ok()
.as_deref(),
)
})
}
pub(super) fn overlap_from_env(value: Option<&str>) -> bool {
!matches!(
value
.unwrap_or_default()
.trim()
.to_ascii_lowercase()
.as_str(),
"0" | "false" | "off" | "no"
)
}