use crate::time::as_millis_u64;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct SupervisorConfig {
pub name: &'static str,
pub tcb_critical: bool,
pub trip_after: u32,
pub max_restarts: u32,
pub base_backoff: Duration,
pub max_backoff: Duration,
}
impl SupervisorConfig {
#[must_use]
pub fn new(name: &'static str, tcb_critical: bool) -> Self {
Self {
name,
tcb_critical,
trip_after: 3,
max_restarts: 10,
base_backoff: Duration::from_millis(100),
max_backoff: Duration::from_secs(60),
}
}
}
pub enum SupervisedOutcome {
Shutdown,
Continue,
Restart,
}
#[must_use]
pub(crate) fn backoff_delay(config: &SupervisorConfig, count: u32) -> Duration {
let shift = count.saturating_sub(1);
let factor = 1u64.checked_shl(shift).unwrap_or(u64::MAX);
let base_ms = as_millis_u64(config.base_backoff);
let uncapped = base_ms.saturating_mul(factor);
let capped = uncapped.min(as_millis_u64(config.max_backoff));
Duration::from_millis(capped)
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
fn config() -> SupervisorConfig {
SupervisorConfig {
name: "test",
tcb_critical: false,
trip_after: 3,
max_restarts: 5,
base_backoff: Duration::from_millis(100),
max_backoff: Duration::from_secs(10),
}
}
#[test]
fn backoff_doubles_from_base() {
let cfg = config();
assert_eq!(backoff_delay(&cfg, 1), Duration::from_millis(100));
assert_eq!(backoff_delay(&cfg, 2), Duration::from_millis(200));
assert_eq!(backoff_delay(&cfg, 3), Duration::from_millis(400));
}
#[test]
fn backoff_saturates_at_max_and_never_overflows() {
let cfg = config();
assert_eq!(backoff_delay(&cfg, 100), cfg.max_backoff);
assert_eq!(backoff_delay(&cfg, u32::MAX), cfg.max_backoff);
}
#[test]
fn default_policy_is_sane() {
let cfg = SupervisorConfig::new("writer", true);
assert!(cfg.tcb_critical);
assert!(cfg.trip_after <= cfg.max_restarts);
assert!(cfg.base_backoff <= cfg.max_backoff);
}
}