use dope_transport::Transport;
use std::time::{Duration, Instant};
pub enum ConnectAction<T: Transport> {
Connect { addr: T::Addr, tag: u32 },
Backoff { until: Instant },
Idle,
}
pub trait ConnectSource<T: Transport>: 'static {
fn poll_connect(&mut self, now: Instant) -> ConnectAction<T>;
fn on_connect_outcome(&mut self, tag: u32, success: bool, now: Instant);
fn on_disconnect(&mut self, tag: u32, now: Instant) {
let _ = (tag, now);
}
}
#[derive(Default, Clone, Copy)]
struct BackoffSlot {
failed_at: Option<Instant>,
attempt: u8,
}
const MAX_BACKOFF_SHIFT: u8 = 10;
pub struct StaticUpstreams<T: Transport> {
upstreams: Vec<T::Addr>,
backoff: Vec<BackoffSlot>,
base_window: Duration,
next: u32,
rng_state: u64,
}
impl<T: Transport> StaticUpstreams<T>
where
T::Addr: Clone,
{
pub fn new(upstreams: Vec<T::Addr>, base_window: Duration) -> Self {
let n = upstreams.len();
let seed = (n as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ base_window.as_nanos() as u64;
Self {
upstreams,
backoff: vec![BackoffSlot::default(); n],
base_window,
next: 0,
rng_state: seed.max(1),
}
}
#[inline(always)]
fn next_rand(&mut self) -> u64 {
let mut x = self.rng_state;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.rng_state = x;
x.wrapping_mul(0x2545F4914F6CDD1D)
}
fn retry_at(&mut self, slot: BackoffSlot) -> Option<Instant> {
let failed_at = slot.failed_at?;
let shift = slot.attempt.min(MAX_BACKOFF_SHIFT) as u32;
let scaled = self.base_window.saturating_mul(1u32 << shift);
let r = self.next_rand();
let jitter_quarters = (r % 4) as u32;
let jitter = scaled / 4 * jitter_quarters;
Some(failed_at + scaled / 2 + jitter)
}
}
impl<T: Transport> ConnectSource<T> for StaticUpstreams<T>
where
T::Addr: Clone,
{
fn poll_connect(&mut self, now: Instant) -> ConnectAction<T> {
if self.upstreams.is_empty() {
return ConnectAction::Idle;
}
let n = self.upstreams.len() as u32;
let mut earliest_retry: Option<Instant> = None;
for offset in 0..n {
let idx = ((self.next + offset) % n) as usize;
let slot = self.backoff[idx];
if slot.failed_at.is_none() {
let addr = self.upstreams[idx].clone();
self.next = (idx as u32 + 1) % n;
return ConnectAction::Connect {
addr,
tag: idx as u32,
};
}
let retry = self.retry_at(slot).expect("failed_at present");
if retry <= now {
let addr = self.upstreams[idx].clone();
self.backoff[idx].failed_at = None;
self.next = (idx as u32 + 1) % n;
return ConnectAction::Connect {
addr,
tag: idx as u32,
};
}
earliest_retry = Some(earliest_retry.map_or(retry, |e| e.min(retry)));
}
match earliest_retry {
Some(until) => ConnectAction::Backoff { until },
None => ConnectAction::Idle,
}
}
fn on_connect_outcome(&mut self, tag: u32, success: bool, now: Instant) {
let idx = tag as usize;
if idx >= self.backoff.len() {
return;
}
if success {
self.backoff[idx] = BackoffSlot::default();
} else {
let attempt = self.backoff[idx]
.attempt
.saturating_add(1)
.min(MAX_BACKOFF_SHIFT);
self.backoff[idx] = BackoffSlot {
failed_at: Some(now),
attempt,
};
}
}
fn on_disconnect(&mut self, tag: u32, now: Instant) {
let idx = tag as usize;
if idx >= self.backoff.len() {
return;
}
self.backoff[idx] = BackoffSlot {
failed_at: Some(now),
attempt: 1,
};
}
}