mod children;
pub use children::{Child, Console, Ended, Window, supervise};
use kernel::{AxError, TimeMs};
pub(crate) const CRASH_WINDOW_MS: u64 = 60_000;
pub(crate) const CRASH_LIMIT: usize = 3;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CrashBudget {
crashes: Vec<TimeMs>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Next {
Stop,
Restart(CrashBudget),
Degraded { crashes: usize, cause: String },
}
impl CrashBudget {
pub(crate) fn fresh() -> Self {
Self {
crashes: Vec::with_capacity(CRASH_LIMIT),
}
}
pub(crate) fn after(mut self, served: &Result<(), AxError>, at: TimeMs) -> Next {
match served {
Ok(()) => Next::Stop,
Err(failure) => {
self.crashes
.retain(|crash| at.value().saturating_sub(crash.value()) < CRASH_WINDOW_MS);
self.crashes.push(at);
if self.crashes.len() >= CRASH_LIMIT {
Next::Degraded {
crashes: self.crashes.len(),
cause: failure.to_string(),
}
} else {
Next::Restart(self)
}
}
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod tests {
use super::{CrashBudget, Next};
use kernel::{AxCode, AxError, TimeMs};
fn crash() -> Result<(), AxError> {
Err(AxError::failure(
AxCode::StorageFatal,
"serve a supervised city",
"exit code: 101",
)
.with_recovery("start it again"))
}
fn verdicts(seconds: &[u64]) -> Vec<Next> {
let mut budget = CrashBudget::fresh();
let mut seen = Vec::new();
for s in seconds {
let next = budget.clone().after(&crash(), TimeMs::new(s * 1_000));
if let Next::Restart(kept) = &next {
budget = kept.clone();
}
seen.push(next);
}
seen
}
fn restart(seconds: &[u64]) -> Next {
let mut budget = CrashBudget::fresh();
budget.crashes = seconds.iter().map(|s| TimeMs::new(s * 1_000)).collect();
Next::Restart(budget)
}
#[test]
fn three_crashes_inside_a_minute_degrade_and_older_ones_expire() {
let degraded = Next::Degraded {
crashes: 3,
cause: crash().unwrap_err().to_string(),
};
assert_eq!(
(
verdicts(&[0, 10, 20]),
verdicts(&[0, 10, 60]),
CrashBudget::fresh().after(&Ok(()), TimeMs::new(5)),
),
(
vec![restart(&[0]), restart(&[0, 10]), degraded],
vec![restart(&[0]), restart(&[0, 10]), restart(&[10, 60])],
Next::Stop,
)
);
}
}