use std::{
cmp::Ordering,
time::{Duration, Instant},
};
use strum::EnumDiscriminants;
#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumDiscriminants)]
#[non_exhaustive]
#[strum_discriminants(derive(Ord, PartialOrd))]
#[strum_discriminants(vis())]
pub enum RetryTime {
Immediate,
AfterWaiting,
After(Duration),
At(Instant),
Never,
}
pub trait HasRetryTime {
fn retry_time(&self) -> RetryTime;
fn abs_retry_time<F>(&self, now: Instant, choose_delay: F) -> AbsRetryTime
where
F: FnOnce() -> Duration,
{
self.retry_time().absolute(now, choose_delay)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[allow(clippy::exhaustive_enums)]
pub enum AbsRetryTime {
Immediate,
At(Instant),
Never,
}
impl AbsRetryTime {
fn from_sum(base: Instant, plus: Duration) -> Self {
match base.checked_add(plus) {
Some(t) => AbsRetryTime::At(t),
None => AbsRetryTime::Never,
}
}
}
impl RetryTime {
pub fn absolute<F>(self, now: Instant, choose_delay: F) -> AbsRetryTime
where
F: FnOnce() -> Duration,
{
match self {
RetryTime::Immediate => AbsRetryTime::Immediate,
RetryTime::AfterWaiting => AbsRetryTime::from_sum(now, choose_delay()),
RetryTime::After(d) => AbsRetryTime::from_sum(now, d),
RetryTime::At(t) => AbsRetryTime::At(t),
RetryTime::Never => AbsRetryTime::Never,
}
}
pub fn earliest_absolute<I, F>(items: I, now: Instant, choose_delay: F) -> Option<AbsRetryTime>
where
I: Iterator<Item = RetryTime>,
F: FnOnce() -> Duration,
{
let chosen_delay =
once_cell::unsync::Lazy::new(|| AbsRetryTime::from_sum(now, choose_delay()));
items
.map(|item| match item {
RetryTime::AfterWaiting => *chosen_delay,
other => other.absolute(now, || unreachable!()),
})
.min()
}
pub fn earliest_approx<I>(items: I) -> Option<RetryTime>
where
I: Iterator<Item = RetryTime>,
{
items.min_by(|a, b| a.loose_cmp(b))
}
pub fn loose_cmp(&self, other: &Self) -> Ordering {
use RetryTime as RT;
match (self, other) {
(RT::After(d1), RetryTime::After(d2)) => d1.cmp(d2),
(RT::At(t1), RetryTime::At(t2)) => t1.cmp(t2),
(a, b) => RetryTimeDiscriminants::from(a).cmp(&RetryTimeDiscriminants::from(b)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn comparison() {
use RetryTime as RT;
let sec = Duration::from_secs(1);
let now = Instant::now();
let sorted = vec![
RT::Immediate,
RT::AfterWaiting,
RT::After(sec * 10),
RT::After(sec * 20),
RT::At(now),
RT::At(now + sec * 30),
RT::Never,
];
for (i, a) in sorted.iter().enumerate() {
for (j, b) in sorted.iter().enumerate() {
assert_eq!(a.loose_cmp(b), i.cmp(&j));
}
}
}
#[test]
fn abs_comparison() {
use AbsRetryTime as ART;
let sec = Duration::from_secs(1);
let now = Instant::now();
let sorted = vec![
ART::Immediate,
ART::At(now),
ART::At(now + sec * 30),
ART::Never,
];
for (i, a) in sorted.iter().enumerate() {
for (j, b) in sorted.iter().enumerate() {
assert_eq!(a.cmp(b), i.cmp(&j));
}
}
}
#[test]
fn earliest_absolute() {
let sec = Duration::from_secs(1);
let now = Instant::now();
let times = vec![RetryTime::AfterWaiting, RetryTime::Never];
let earliest = RetryTime::earliest_absolute(times.into_iter(), now, || sec);
assert_eq!(
earliest.expect("no absolute time"),
AbsRetryTime::At(now + sec)
);
}
#[test]
fn abs_from_sum() {
let base = Instant::now();
let delta = Duration::from_secs(1);
assert_eq!(
AbsRetryTime::from_sum(base, delta),
AbsRetryTime::At(base + delta)
);
assert_eq!(
AbsRetryTime::from_sum(base, Duration::MAX),
AbsRetryTime::Never
);
}
}