use std::sync::OnceLock;
use std::time::{Duration, Instant};
pub trait MonotonicTimer: std::fmt::Debug + Send + Sync {
fn monotonic_now_ms(&self) -> u64;
}
#[derive(Debug, Default)]
pub struct StdMonotonicTimer {
origin: OnceLock<Instant>,
}
impl MonotonicTimer for StdMonotonicTimer {
fn monotonic_now_ms(&self) -> u64 {
#[allow(clippy::disallowed_methods)]
let now = Instant::now();
let origin = self.origin.get_or_init(|| now);
u64::try_from(now.saturating_duration_since(*origin).as_millis()).unwrap_or(u64::MAX)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransportRetryPolicy {
pub max_retries: u32,
pub initial_backoff: Duration,
pub max_backoff: Duration,
pub operation_deadline: Duration,
}
pub struct OperationDeadline<'timer> {
timer: &'timer dyn MonotonicTimer,
started_ms: u64,
deadline: Duration,
}
impl<'timer> OperationDeadline<'timer> {
pub fn start(timer: &'timer dyn MonotonicTimer, deadline: Duration) -> Self {
Self {
timer,
started_ms: timer.monotonic_now_ms(),
deadline,
}
}
pub fn remaining(&self) -> Option<Duration> {
let elapsed_ms = self
.timer
.monotonic_now_ms()
.saturating_sub(self.started_ms);
let deadline_ms = u64::try_from(self.deadline.as_millis()).unwrap_or(u64::MAX);
if elapsed_ms >= deadline_ms {
return None;
}
Some(Duration::from_millis(deadline_ms - elapsed_ms))
}
pub fn deadline(&self) -> Duration {
self.deadline
}
}
pub fn transport_retry_backoff(policy: &TransportRetryPolicy, retry: u32) -> Duration {
let doublings = retry.saturating_sub(1).min(16);
policy
.initial_backoff
.saturating_mul(1u32 << doublings)
.min(policy.max_backoff)
}