use std::sync::{Arc, OnceLock, RwLock};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct StartupReadyGate {
pub plugin_id: String,
pub signal: String,
pub timeout: Duration,
}
#[derive(Default)]
pub struct StartupReadyGateRegistry {
entries: RwLock<Vec<StartupReadyGate>>,
}
impl std::fmt::Debug for StartupReadyGateRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let count = self.entries.read().map_or(0, |g| g.len());
f.debug_struct("StartupReadyGateRegistry")
.field("entries", &count)
.finish()
}
}
impl StartupReadyGateRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, gate: StartupReadyGate) {
if let Ok(mut guard) = self.entries.write() {
if let Some(existing) = guard.iter_mut().find(|existing| {
existing.plugin_id == gate.plugin_id && existing.signal == gate.signal
}) {
*existing = gate;
return;
}
guard.push(gate);
}
}
#[must_use]
pub fn snapshot(&self) -> Vec<StartupReadyGate> {
self.entries.read().map(|g| g.clone()).unwrap_or_default()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.read().map_or(0, |g| g.len())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[must_use]
pub fn global_startup_ready_gate_registry() -> Arc<StartupReadyGateRegistry> {
static GLOBAL: OnceLock<Arc<StartupReadyGateRegistry>> = OnceLock::new();
GLOBAL
.get_or_init(|| Arc::new(StartupReadyGateRegistry::new()))
.clone()
}
pub fn register_startup_ready_gate(plugin_id: &str, signal: &str, timeout: Duration) {
global_startup_ready_gate_registry().register(StartupReadyGate {
plugin_id: plugin_id.to_string(),
signal: signal.to_string(),
timeout,
});
}
#[must_use]
pub fn registered_startup_ready_gates() -> Vec<StartupReadyGate> {
global_startup_ready_gate_registry().snapshot()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_tracks_entries() {
let registry = StartupReadyGateRegistry::new();
assert!(registry.is_empty());
registry.register(StartupReadyGate {
plugin_id: "plug.a".to_string(),
signal: "ready".to_string(),
timeout: Duration::from_millis(500),
});
registry.register(StartupReadyGate {
plugin_id: "plug.b".to_string(),
signal: "warm".to_string(),
timeout: Duration::from_millis(100),
});
assert_eq!(registry.len(), 2);
let snap = registry.snapshot();
assert_eq!(snap[0].plugin_id, "plug.a");
assert_eq!(snap[1].signal, "warm");
}
#[test]
fn registry_replaces_duplicate_gate() {
let registry = StartupReadyGateRegistry::new();
registry.register(StartupReadyGate {
plugin_id: "plug.a".to_string(),
signal: "ready".to_string(),
timeout: Duration::from_millis(500),
});
registry.register(StartupReadyGate {
plugin_id: "plug.a".to_string(),
signal: "ready".to_string(),
timeout: Duration::from_millis(25),
});
let snap = registry.snapshot();
assert_eq!(snap.len(), 1);
assert_eq!(snap[0].timeout, Duration::from_millis(25));
}
}