#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum StallAction {
WarmCutover,
Resume,
Repair,
RelayEscalate,
ExhaustedRelayForbidden,
ExhaustedAlreadyRelay,
}
pub fn decide_stall_action(
attempt: u32,
max_repairs: u32,
warm_eligible: bool,
relay_forbidden: bool,
already_relayed: bool,
) -> StallAction {
if attempt == 0 {
return if warm_eligible { StallAction::WarmCutover } else { StallAction::Resume };
}
if attempt >= max_repairs {
if relay_forbidden {
return StallAction::ExhaustedRelayForbidden;
}
if already_relayed {
return StallAction::ExhaustedAlreadyRelay;
}
return StallAction::RelayEscalate;
}
StallAction::Repair
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Liveness {
Idle,
Establishing,
Flowing,
Stalled,
}
#[derive(Debug, Clone, Copy)]
pub struct LiveObs {
pub in_flight: bool,
pub transport_up: bool,
pub flowed: bool,
pub idle_ms: u64,
pub grace_ms: u64,
pub stall_ms: u64,
}
pub fn phase_threshold(flowed: bool, grace_ms: u64, stall_ms: u64) -> u64 {
if flowed {
stall_ms
} else {
grace_ms
}
}
pub fn classify(o: &LiveObs) -> Liveness {
if !o.in_flight {
return Liveness::Idle;
}
if !o.transport_up {
return Liveness::Establishing;
}
if !o.flowed {
let grace = phase_threshold(false, o.grace_ms, o.stall_ms);
return if o.idle_ms >= grace {
Liveness::Stalled
} else {
Liveness::Establishing
};
}
let thr = phase_threshold(true, o.grace_ms, o.stall_ms);
if o.idle_ms >= thr {
Liveness::Stalled
} else {
Liveness::Flowing
}
}
#[cfg(test)]
mod tests {
use super::*;
const MAX: u32 = 5;
#[test]
fn attempt0_warm_eligible_cuts_over() {
assert_eq!(decide_stall_action(0, MAX, true, false, false), StallAction::WarmCutover);
}
#[test]
fn attempt0_not_warm_resumes() {
assert_eq!(decide_stall_action(0, MAX, false, false, false), StallAction::Resume);
}
#[test]
fn middle_attempts_repair_in_place() {
for a in 1..MAX {
assert_eq!(decide_stall_action(a, MAX, false, false, false), StallAction::Repair);
}
}
#[test]
fn exhausted_escalates_to_relay_when_permitted() {
assert_eq!(decide_stall_action(MAX, MAX, false, false, false), StallAction::RelayEscalate);
assert_eq!(decide_stall_action(MAX + 3, MAX, false, false, false), StallAction::RelayEscalate);
}
#[test]
fn exhausted_with_no_relay_fails_clean() {
assert_eq!(decide_stall_action(MAX, MAX, false, true, false), StallAction::ExhaustedRelayForbidden);
}
#[test]
fn exhausted_already_on_relay_fails_clean() {
assert_eq!(decide_stall_action(MAX, MAX, false, false, true), StallAction::ExhaustedAlreadyRelay);
}
#[test]
fn no_relay_takes_priority_over_already_relayed_at_exhaustion() {
assert_eq!(decide_stall_action(MAX, MAX, false, true, true), StallAction::ExhaustedRelayForbidden);
}
#[test]
fn warm_cutover_only_on_first_attempt() {
assert_eq!(decide_stall_action(1, MAX, true, false, false), StallAction::Repair);
assert_eq!(decide_stall_action(MAX, MAX, true, false, false), StallAction::RelayEscalate);
}
const GRACE: u64 = 45_000;
const STALL: u64 = 6_000;
fn obs(in_flight: bool, transport_up: bool, flowed: bool, idle_ms: u64) -> LiveObs {
LiveObs { in_flight, transport_up, flowed, idle_ms, grace_ms: GRACE, stall_ms: STALL }
}
#[test]
fn no_transfer_is_idle_regardless_of_anything() {
assert_eq!(classify(&obs(false, true, true, u64::MAX)), Liveness::Idle);
assert_eq!(classify(&obs(false, false, false, 0)), Liveness::Idle);
}
#[test]
fn no_transport_is_always_establishing() {
for idle in [0u64, STALL + 1, GRACE, u64::MAX] {
assert_eq!(classify(&obs(true, false, false, idle)), Liveness::Establishing);
}
}
#[test]
fn pre_first_byte_is_judged_by_grace_not_stall() {
let o = obs(true, true, false, STALL + 1);
assert_eq!(classify(&o), Liveness::Establishing);
assert_eq!(classify(&obs(true, true, false, GRACE - 1)), Liveness::Establishing);
assert_eq!(classify(&obs(true, true, false, GRACE)), Liveness::Stalled);
}
#[test]
fn flowing_link_uses_the_tight_threshold() {
assert_eq!(classify(&obs(true, true, true, 0)), Liveness::Flowing);
assert_eq!(classify(&obs(true, true, true, STALL - 1)), Liveness::Flowing);
assert_eq!(classify(&obs(true, true, true, STALL)), Liveness::Stalled);
}
#[test]
fn dead_transport_idle_max_is_stalled_when_flowed() {
assert_eq!(classify(&obs(true, true, true, u64::MAX)), Liveness::Stalled);
assert_eq!(classify(&obs(true, true, false, u64::MAX)), Liveness::Stalled);
}
#[test]
fn phase_threshold_is_grace_before_first_byte_stall_after() {
assert_eq!(phase_threshold(false, GRACE, STALL), GRACE);
assert_eq!(phase_threshold(true, GRACE, STALL), STALL);
}
#[test]
fn repair_re_enters_establishing_grace_breaking_the_loop() {
assert_eq!(classify(&obs(true, true, true, STALL)), Liveness::Stalled); assert_eq!(classify(&obs(true, true, false, STALL * 3)), Liveness::Establishing);
assert_eq!(classify(&obs(true, true, true, 0)), Liveness::Flowing);
}
#[test]
fn grace_never_below_stall_via_classify_inputs() {
let weird = LiveObs { in_flight: true, transport_up: true, flowed: false, idle_ms: 5_000, grace_ms: 1_000, stall_ms: 6_000 };
assert_eq!(classify(&weird), Liveness::Stalled);
}
}