use std::collections::BTreeSet;
use std::sync::Mutex;
use uuid::Uuid;
#[derive(Default)]
struct Ledger {
interrupted: BTreeSet<Uuid>,
resumed: BTreeSet<Uuid>,
delivered: usize,
failed: usize,
tracking_disabled: Option<String>,
}
static LEDGER: Mutex<Option<Ledger>> = Mutex::new(None);
fn with<R>(f: impl FnOnce(&mut Ledger) -> R) -> Option<R> {
match LEDGER.lock() {
Ok(mut guard) => Some(f(guard.get_or_insert_with(Ledger::default))),
Err(e) => {
tracing::warn!("Boot report ledger is unreadable, the summary will be short: {e}");
None
}
}
}
pub fn record_interrupted(session_id: Uuid) {
with(|l| l.interrupted.insert(session_id));
}
pub fn record_resumed(session_id: Uuid) {
with(|l| l.resumed.insert(session_id));
}
pub fn record_delivered() {
with(|l| l.delivered += 1);
}
pub fn record_failed() {
with(|l| l.failed += 1);
}
pub fn record_tracking_disabled(reason: String) {
with(|l| l.tracking_disabled = Some(reason));
}
pub fn summary_line() -> String {
let (interrupted, resumed, delivered, failed, disabled) = with(|l| {
(
l.interrupted.len(),
l.resumed.iter().map(Uuid::to_string).collect::<Vec<_>>(),
l.delivered,
l.failed,
l.tracking_disabled.clone(),
)
})
.unwrap_or_default();
let mut line = format!(
"[boot] interrupted={interrupted} resumed=[{}] delivered={delivered} failed={failed}",
resumed.join(" ")
);
if let Some(reason) = disabled {
line.push_str(&format!(" recovery=DISABLED({reason})"));
}
line
}
pub fn schedule_summary(after: std::time::Duration) {
tokio::spawn(async move {
tokio::time::sleep(after).await;
tracing::info!(target: "background_task", "{}", summary_line());
});
}
#[cfg(test)]
pub(crate) fn reset_for_test() {
if let Ok(mut guard) = LEDGER.lock() {
*guard = None;
}
}