use core::ops::{Add, Mul, RangeInclusive, Sub};
use embedded_time::duration::Milliseconds;
use embedded_time::Instant;
use naan::prelude::Monad;
use rand::{Rng, SeedableRng};
use crate::time::{Clock, Millis};
#[derive(Debug)]
pub struct RetryTimer<C: Clock> {
start: Instant<C>,
last_attempted_at: Option<Instant<C>>,
init: Millis,
strategy: Strategy,
attempts: Attempts,
max_attempts: Attempts,
}
impl<C> RetryTimer<C> where C: Clock
{
pub fn new(start: Instant<C>, strategy: Strategy, max_attempts: Attempts) -> Self {
Self { start,
strategy,
last_attempted_at: None,
init: if strategy.has_jitter() {
let mut rand =
Ok(start.duration_since_epoch()).bind(Millis::try_from)
.map(|Milliseconds(ms)| {
rand_chacha::ChaCha8Rng::seed_from_u64(ms)
})
.unwrap();
Milliseconds(rand.gen_range(strategy.range()))
} else {
Milliseconds(*strategy.range().start())
},
max_attempts,
attempts: Attempts(1) }
}
pub fn what_should_i_do(&mut self,
now: Instant<C>)
-> nb::Result<YouShould, core::convert::Infallible> {
if self.attempts >= self.max_attempts {
Ok(YouShould::Cry)
} else {
if now >= self.next_attempt_at() {
self.attempts.0 += 1;
self.last_attempted_at = Some(now);
Ok(YouShould::Retry)
} else {
Err(nb::Error::WouldBlock)
}
}
}
pub fn first_attempted_at(&self) -> Instant<C> {
self.start
}
pub fn last_attempted_at(&self) -> Instant<C> {
self.last_attempted_at
.unwrap_or_else(|| self.first_attempted_at())
}
pub fn next_attempt_at(&self) -> Instant<C> {
let after_start = match self.strategy {
| Strategy::Delay { .. } => Milliseconds(self.init.0 * (self.attempts.0 as u64)),
| Strategy::Exponential { .. } => {
Milliseconds(Strategy::total_delay_exp(self.init, self.attempts.0))
},
};
self.start + after_start
}
}
impl<C> Copy for RetryTimer<C> where C: Clock {}
impl<C> Clone for RetryTimer<C> where C: Clock
{
fn clone(&self) -> Self {
Self { start: self.start,
init: self.init,
last_attempted_at: self.last_attempted_at,
strategy: self.strategy,
attempts: self.attempts,
max_attempts: self.max_attempts }
}
}
impl<C> PartialEq for RetryTimer<C> where C: Clock
{
fn eq(&self, other: &Self) -> bool {
self.start == other.start
&& self.init == other.init
&& self.last_attempted_at == other.last_attempted_at
&& self.strategy == other.strategy
&& self.attempts == other.attempts
&& self.max_attempts == other.max_attempts
}
}
impl<C> Eq for RetryTimer<C> where C: Clock {}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Attempts(pub u16);
impl Add for Attempts {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub for Attempts {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Mul for Attempts {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0)
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum YouShould {
Cry,
Retry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Strategy {
Exponential {
init_min: Millis,
init_max: Millis,
},
Delay {
min: Millis,
max: Millis,
},
}
impl Strategy {
pub fn has_jitter(&self) -> bool {
let rng = self.range();
rng.start() != rng.end()
}
pub fn range(&self) -> RangeInclusive<u64> {
match self {
| &Self::Delay { min: Milliseconds(min),
max: Milliseconds(max), } => min..=max,
| &Self::Exponential { init_min: Milliseconds(min),
init_max: Milliseconds(max), } => min..=max,
}
}
pub fn max_time(&self, max_attempts: Attempts) -> Millis {
Milliseconds(match self {
| Self::Exponential { init_max, .. } => {
Self::total_delay_exp(*init_max, max_attempts.0)
},
| Self::Delay { max: Milliseconds(max),
.. } => max * max_attempts.0 as u64,
})
}
const fn total_delay_exp(Milliseconds(init): Millis, attempt: u16) -> u64 {
init * 2u64.pow((attempt - 1) as u32)
}
}
#[cfg(test)]
mod test {
use embedded_time::rate::Fraction;
use embedded_time::Clock;
use super::*;
#[derive(Debug)]
pub struct FakeClock(pub *const u64);
impl FakeClock {
pub fn new(time_ptr: *const u64) -> Self {
Self(time_ptr)
}
}
impl embedded_time::Clock for FakeClock {
type T = u64;
const SCALING_FACTOR: Fraction = Fraction::new(1, 1000);
fn try_now(&self) -> Result<Instant<Self>, embedded_time::clock::Error> {
unsafe { Ok(Instant::new(*self.0)) }
}
}
#[test]
fn delay_retrier() {
#![allow(unused_assignments)]
let mut time_millis = 0u64;
let clock = FakeClock::new(&time_millis as *const _);
let now = || clock.try_now().unwrap();
let mut retry = RetryTimer::new(now(),
Strategy::Delay { min: Milliseconds(1000),
max: Milliseconds(1000) },
Attempts(5));
time_millis = 999;
assert_eq!(retry.what_should_i_do(now()).unwrap_err(),
nb::Error::WouldBlock);
time_millis = 1000;
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 1999;
assert_eq!(retry.what_should_i_do(now()).unwrap_err(),
nb::Error::WouldBlock);
time_millis = 2000;
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 10_000;
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Cry);
}
#[test]
fn exponential_retrier() {
#![allow(unused_assignments)]
let mut time_millis = 0u64;
let clock = FakeClock::new(&time_millis as *const _);
let now = || clock.try_now().unwrap();
let mut retry = RetryTimer::new(now(),
Strategy::Exponential { init_min: Milliseconds(1000),
init_max: Milliseconds(1000) },
Attempts(6));
time_millis = 999;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap_err(),
nb::Error::WouldBlock);
time_millis = 1000;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 1999;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap_err(),
nb::Error::WouldBlock);
time_millis = 2000;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 3999;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap_err(),
nb::Error::WouldBlock);
time_millis = 4000;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 8_000;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
time_millis = 16_000;
println!("{}", time_millis);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Retry);
assert_eq!(retry.what_should_i_do(now()).unwrap(), YouShould::Cry);
}
#[test]
fn exp_calculation() {
let init = Milliseconds(100);
assert_eq!(Strategy::total_delay_exp(init, 1), 100);
assert_eq!(Strategy::total_delay_exp(init, 2), 200);
assert_eq!(Strategy::total_delay_exp(init, 3), 400);
}
}