use std::time::Duration;
pub(crate) const RETRY_SECONDS: i64 = 60;
pub(crate) const RETRY_MOST_SECONDS: i64 = 900;
pub(crate) const ATTEMPTS: u32 = 3;
pub const DECIDE_EVERY_SECONDS: i64 = 30;
pub(crate) fn retry_after(waits: u32) -> i64 {
(RETRY_SECONDS << waits.saturating_sub(1).min(4)).min(RETRY_MOST_SECONDS)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Refusals {
in_a_row: u32,
at: Duration,
until: Duration,
}
impl Refusals {
pub fn waits(&self, now: Duration) -> bool {
(self.at..self.until).contains(&now)
}
pub fn due(&self, decided: Duration, every: Duration) -> Duration {
let next = decided + every;
if self.waits(decided) {
next.min(self.until)
} else {
next
}
}
pub fn refused(&mut self, at: Duration) -> i64 {
let in_a_row = self.in_a_row + 1;
let wait = retry_after(in_a_row);
*self = Refusals {
in_a_row,
at,
until: at + Duration::from_secs(wait.unsigned_abs()),
};
wait
}
pub fn ended(&mut self) {
*self = Refusals::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
const AT: Duration = Duration::from_secs(1_760_000_000);
const EVERY: Duration = Duration::from_secs(30);
fn secs(seconds: i64) -> Duration {
Duration::from_secs(seconds.unsigned_abs())
}
#[test]
fn the_wait_doubles_from_a_minute_to_a_quarter_of_an_hour() {
let waits: Vec<i64> = (0..8).map(retry_after).collect();
assert_eq!(waits, [60, 60, 120, 240, 480, 900, 900, 900]);
}
#[test]
fn a_refusal_is_tried_again_only_after_the_cores_wait() {
let mut refusals = Refusals::default();
assert!(!refusals.waits(AT));
assert_eq!(refusals.due(AT, EVERY), AT + EVERY);
assert_eq!(refusals.refused(AT), 60);
assert!(refusals.waits(AT + secs(59)));
assert!(!refusals.waits(AT + secs(60)));
assert!(
!refusals.waits(AT - secs(1)),
"a clock gone backwards waits on nothing"
);
assert_eq!(refusals.due(AT, EVERY), AT + EVERY, "it looks meanwhile");
assert_eq!(
refusals.due(AT + secs(45), EVERY),
AT + secs(60),
"and decides as the wait ends"
);
assert_eq!(refusals.refused(AT + secs(60)), 120);
assert!(refusals.waits(AT + secs(179)));
assert!(!refusals.waits(AT + secs(180)));
for _ in 0..8 {
refusals.refused(AT);
}
assert!(refusals.waits(AT + secs(RETRY_MOST_SECONDS - 1)));
assert!(
!refusals.waits(AT + secs(RETRY_MOST_SECONDS)),
"at most a quarter of an hour"
);
refusals.ended();
assert!(!refusals.waits(AT + secs(200)));
refusals.refused(AT + secs(200));
assert!(
!refusals.waits(AT + secs(260)),
"a row ended starts again at a minute"
);
}
}