lean_ctx/core/shadow/
runtime.rs1use super::{ShadowEngine, ShadowReport, ShadowTask};
3use crate::core::config::{Config, ShadowConfig};
4use std::{
5 panic::{AssertUnwindSafe, catch_unwind},
6 sync::{Mutex, OnceLock},
7};
8
9pub use super::persistence::{list_reports, load_report, persist_report};
10
11#[derive(Default)]
12pub(crate) struct State {
13 tasks: Vec<ShadowTask>,
14 latest: Option<ShadowReport>,
15}
16pub(crate) static STATE: OnceLock<Mutex<State>> = OnceLock::new();
17pub(crate) fn state() -> &'static Mutex<State> {
18 STATE.get_or_init(|| Mutex::new(State::default()))
19}
20
21pub struct ShadowRuntime;
23impl ShadowRuntime {
24 pub fn is_enabled() -> bool {
25 catch_unwind(AssertUnwindSafe(|| load_shadow_config().enabled)).unwrap_or(false)
26 }
27 pub fn on_task_complete(task: &ShadowTask) {
28 let _ = catch_unwind(AssertUnwindSafe(|| Self::on_task_complete_inner(task)));
29 }
30 pub fn get_latest_report() -> Option<ShadowReport> {
31 catch_unwind(AssertUnwindSafe(|| {
32 state()
33 .lock()
34 .ok()
35 .and_then(|state| state.latest.clone())
36 .or_else(|| list_reports().last().and_then(|path| load_report(path)))
37 }))
38 .unwrap_or(None)
39 }
40 pub fn force_report() -> Option<ShadowReport> {
41 catch_unwind(AssertUnwindSafe(Self::report_pending)).unwrap_or(None)
42 }
43
44 fn on_task_complete_inner(task: &ShadowTask) {
45 if !Self::is_enabled() {
46 return;
47 }
48 let interval = load_shadow_config().report_interval.max(1);
49 let ready = state().lock().ok().and_then(|mut state| {
50 state.tasks.push(task.clone());
51 (state.tasks.len() >= interval).then(|| std::mem::take(&mut state.tasks))
52 });
53 if let Some(tasks) = ready {
54 let _ = Self::generate_report(&tasks);
55 }
56 }
57
58 fn report_pending() -> Option<ShadowReport> {
59 let tasks = state()
60 .lock()
61 .ok()
62 .map(|mut state| std::mem::take(&mut state.tasks))?;
63 Self::generate_report(&tasks)
64 }
65
66 fn generate_report(tasks: &[ShadowTask]) -> Option<ShadowReport> {
67 if tasks.is_empty() {
68 return None;
69 }
70 let report = ShadowEngine::run_comparison(tasks);
72 let _ = persist_report(&report);
73 if let Ok(mut state) = state().lock() {
74 state.latest = Some(report.clone());
75 }
76 Some(report)
77 }
78}
79pub fn load_shadow_config() -> ShadowConfig {
80 catch_unwind(AssertUnwindSafe(|| Config::load().shadow)).unwrap_or_default()
81}