use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum TimerKind {
Liveness,
CloseLinger,
Contested,
Loss,
Pto,
AckDelay,
Keepalive,
PersistentKeepalive,
}
impl TimerKind {
pub(crate) const ALL: [TimerKind; 8] = [
TimerKind::Liveness,
TimerKind::CloseLinger,
TimerKind::Contested,
TimerKind::Loss,
TimerKind::Pto,
TimerKind::AckDelay,
TimerKind::Keepalive,
TimerKind::PersistentKeepalive,
];
const fn index(self) -> usize {
self as usize
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Due(u8);
impl Due {
pub(crate) const fn contains(self, kind: TimerKind) -> bool {
self.0 & (1 << kind.index()) != 0
}
pub(crate) const fn is_empty(self) -> bool {
self.0 == 0
}
pub(crate) const fn len(self) -> u32 {
self.0.count_ones()
}
pub(crate) fn iter(self) -> impl Iterator<Item = TimerKind> {
TimerKind::ALL
.into_iter()
.filter(move |k| self.contains(*k))
}
fn insert(&mut self, kind: TimerKind) {
self.0 |= 1 << kind.index();
}
fn remove(&mut self, kind: TimerKind) {
self.0 &= !(1 << kind.index());
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Timers([Option<Instant>; 8]);
impl Timers {
pub(crate) const fn new() -> Self {
Self([None; 8])
}
pub(crate) fn get(&self, kind: TimerKind) -> Option<Instant> {
self.0[kind.index()]
}
pub(crate) fn arm(&mut self, kind: TimerKind, at: Instant) {
self.0[kind.index()] = Some(at);
}
pub(crate) fn set(&mut self, kind: TimerKind, at: Option<Instant>) {
self.0[kind.index()] = at;
}
pub(crate) fn disarm(&mut self, kind: TimerKind) {
self.0[kind.index()] = None;
}
pub(crate) fn disarm_all_except(&mut self, keep: TimerKind) {
for kind in TimerKind::ALL {
if kind != keep {
self.disarm(kind);
}
}
}
pub(crate) fn disarm_all(&mut self) {
self.0 = [None; 8];
}
pub(crate) fn next(&self) -> Option<Instant> {
self.0.iter().flatten().copied().min()
}
pub(crate) fn due(&self, now: Instant) -> Due {
let mut due = Due::default();
for kind in TimerKind::ALL {
if self.get(kind).is_some_and(|deadline| deadline <= now) {
due.insert(kind);
}
}
if due.contains(TimerKind::Loss) {
due.remove(TimerKind::Pto);
}
due
}
pub(crate) fn take_due(&mut self, now: Instant) -> Due {
let due = self.due(now);
for kind in due.iter() {
self.disarm(kind);
}
due
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
fn t0() -> Instant {
Instant::now()
}
#[test]
fn declaration_order_is_ruling_76s_order() {
assert!(TimerKind::Liveness < TimerKind::CloseLinger);
assert!(TimerKind::CloseLinger < TimerKind::Contested);
assert!(TimerKind::Contested < TimerKind::Loss);
assert!(TimerKind::Loss < TimerKind::Pto);
assert!(TimerKind::Pto < TimerKind::AckDelay);
assert!(TimerKind::AckDelay < TimerKind::Keepalive);
assert!(TimerKind::Keepalive < TimerKind::PersistentKeepalive);
let mut sorted = TimerKind::ALL;
sorted.sort();
assert_eq!(sorted, TimerKind::ALL);
}
#[test]
fn equal_deadlines_resolve_in_ruling_76s_order() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::PersistentKeepalive, now);
timers.arm(TimerKind::Keepalive, now);
timers.arm(TimerKind::AckDelay, now);
timers.arm(TimerKind::Contested, now);
timers.arm(TimerKind::CloseLinger, now);
timers.arm(TimerKind::Liveness, now);
let order: Vec<TimerKind> = timers.due(now).iter().collect();
assert_eq!(
order,
vec![
TimerKind::Liveness,
TimerKind::CloseLinger,
TimerKind::Contested,
TimerKind::AckDelay,
TimerKind::Keepalive,
TimerKind::PersistentKeepalive,
]
);
}
#[test]
fn liveness_beats_close_linger_and_contested() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::CloseLinger, now);
timers.arm(TimerKind::Liveness, now);
assert_eq!(timers.due(now).iter().next(), Some(TimerKind::Liveness));
let mut timers = Timers::new();
timers.arm(TimerKind::Contested, now);
timers.arm(TimerKind::Liveness, now);
assert_eq!(timers.due(now).iter().next(), Some(TimerKind::Liveness));
}
#[test]
fn loss_and_pto_yield_exactly_one_and_it_is_loss() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::Loss, now);
timers.arm(TimerKind::Pto, now);
let due = timers.due(now);
assert_eq!(due.len(), 1, "exactly one of the pair fires");
assert!(due.contains(TimerKind::Loss));
assert!(!due.contains(TimerKind::Pto));
let taken = timers.take_due(now);
assert_eq!(taken.len(), 1);
assert!(timers.get(TimerKind::Loss).is_none(), "Loss was stopped");
assert_eq!(
timers.get(TimerKind::Pto),
Some(now),
"the suppressed PTO is still armed for the next evaluation"
);
}
#[test]
fn pto_fires_alone() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::Pto, now);
assert_eq!(timers.due(now).iter().next(), Some(TimerKind::Pto));
}
#[test]
fn ack_delay_follows_the_loss_evaluation() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::AckDelay, now);
timers.arm(TimerKind::Loss, now);
let order: Vec<TimerKind> = timers.due(now).iter().collect();
assert_eq!(order, vec![TimerKind::Loss, TimerKind::AckDelay]);
}
#[test]
fn a_deadline_fires_at_d_and_not_before() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::CloseLinger, now + Duration::from_secs(5));
assert!(
timers.due(now + Duration::from_millis(4_999)).is_empty(),
"not before D"
);
assert!(
timers
.due(now + Duration::from_secs(5))
.contains(TimerKind::CloseLinger),
"at D"
);
assert!(
timers
.due(now + Duration::from_millis(5_001))
.contains(TimerKind::CloseLinger),
"and after D"
);
}
#[test]
fn take_due_is_idempotent_at_one_instant() {
let now = t0();
let mut timers = Timers::new();
timers.arm(TimerKind::CloseLinger, now);
assert_eq!(timers.take_due(now).len(), 1);
assert!(timers.take_due(now).is_empty());
}
#[test]
fn next_is_the_minimum_over_armed_timers() {
let now = t0();
let mut timers = Timers::new();
assert_eq!(timers.next(), None);
timers.arm(TimerKind::Keepalive, now + Duration::from_secs(9));
timers.arm(TimerKind::Liveness, now + Duration::from_secs(25));
timers.arm(TimerKind::CloseLinger, now + Duration::from_secs(5));
assert_eq!(timers.next(), Some(now + Duration::from_secs(5)));
timers.disarm(TimerKind::CloseLinger);
assert_eq!(timers.next(), Some(now + Duration::from_secs(9)));
}
#[test]
fn disarm_all_except_keeps_exactly_one() {
let now = t0();
let mut timers = Timers::new();
for kind in TimerKind::ALL {
timers.arm(kind, now);
}
timers.disarm_all_except(TimerKind::CloseLinger);
assert_eq!(timers.get(TimerKind::CloseLinger), Some(now));
for kind in TimerKind::ALL {
if kind != TimerKind::CloseLinger {
assert_eq!(timers.get(kind), None, "{kind:?} survived the post-mortem");
}
}
assert_eq!(timers.next(), Some(now));
}
#[test]
fn set_arms_and_disarms() {
let now = t0();
let mut timers = Timers::new();
timers.set(TimerKind::Liveness, Some(now));
assert_eq!(timers.get(TimerKind::Liveness), Some(now));
timers.set(TimerKind::Liveness, None);
assert_eq!(timers.get(TimerKind::Liveness), None);
}
}