use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum WakeHint {
None,
Deadline(Instant),
GenericRetry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum WakeReason {
Health,
Discovery,
GenericRetry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct ScheduledWake {
pub(super) at: Instant,
pub(super) reason: WakeReason,
}
pub(super) fn schedule_next_wake(
now: Instant,
health_interval: Duration,
hint: WakeHint,
retry_attempt: u64,
) -> ScheduledWake {
let health = ScheduledWake {
at: now + health_interval,
reason: WakeReason::Health,
};
match hint {
WakeHint::None => health,
WakeHint::Deadline(at) => earliest(
health,
ScheduledWake {
at,
reason: WakeReason::Discovery,
},
),
WakeHint::GenericRetry => {
let retry_seconds = (1_u64 << retry_attempt.min(5)).min(30);
let retry = ScheduledWake {
at: now + Duration::from_secs(retry_seconds),
reason: WakeReason::GenericRetry,
};
earliest(health, retry)
}
}
}
fn earliest(current: ScheduledWake, candidate: ScheduledWake) -> ScheduledWake {
if candidate.at < current.at {
candidate
} else {
current
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schedules_earliest_wake() {
let now = Instant::now();
let cases = [
(
"health without retry",
Duration::from_secs(60),
WakeHint::None,
0,
Duration::from_secs(60),
WakeReason::Health,
),
(
"generic retry before health",
Duration::from_secs(60),
WakeHint::GenericRetry,
2,
Duration::from_secs(4),
WakeReason::GenericRetry,
),
(
"health before generic retry",
Duration::from_secs(2),
WakeHint::GenericRetry,
3,
Duration::from_secs(2),
WakeReason::Health,
),
(
"generic retry capped at thirty seconds",
Duration::from_secs(60),
WakeHint::GenericRetry,
10,
Duration::from_secs(30),
WakeReason::GenericRetry,
),
(
"discovery deadline before health",
Duration::from_secs(60),
WakeHint::Deadline(now + Duration::from_secs(30)),
0,
Duration::from_secs(30),
WakeReason::Discovery,
),
(
"health before discovery deadline",
Duration::from_secs(60),
WakeHint::Deadline(now + Duration::from_secs(120)),
0,
Duration::from_secs(60),
WakeReason::Health,
),
];
for (name, health_interval, hint, attempt, expected_delay, expected_reason) in cases {
let scheduled = schedule_next_wake(now, health_interval, hint, attempt);
assert_eq!(
scheduled,
ScheduledWake {
at: now + expected_delay,
reason: expected_reason,
},
"{name}",
);
}
}
}