1use std::sync::OnceLock;
4use std::time::{Duration, Instant};
5
6pub trait MonotonicTimer: std::fmt::Debug + Send + Sync {
8 fn monotonic_now_ms(&self) -> u64;
10}
11
12#[derive(Debug, Default)]
14pub struct StdMonotonicTimer {
15 origin: OnceLock<Instant>,
16}
17
18impl MonotonicTimer for StdMonotonicTimer {
19 fn monotonic_now_ms(&self) -> u64 {
20 #[allow(clippy::disallowed_methods)]
22 let now = Instant::now();
23 let origin = self.origin.get_or_init(|| now);
24 u64::try_from(now.saturating_duration_since(*origin).as_millis()).unwrap_or(u64::MAX)
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct TransportRetryPolicy {
31 pub max_retries: u32,
33 pub initial_backoff: Duration,
35 pub max_backoff: Duration,
37 pub operation_deadline: Duration,
39}
40
41pub struct OperationDeadline<'timer> {
43 timer: &'timer dyn MonotonicTimer,
44 started_ms: u64,
45 deadline: Duration,
46}
47
48impl<'timer> OperationDeadline<'timer> {
49 pub fn start(timer: &'timer dyn MonotonicTimer, deadline: Duration) -> Self {
51 Self {
52 timer,
53 started_ms: timer.monotonic_now_ms(),
54 deadline,
55 }
56 }
57
58 pub fn remaining(&self) -> Option<Duration> {
60 let elapsed_ms = self
61 .timer
62 .monotonic_now_ms()
63 .saturating_sub(self.started_ms);
64 let deadline_ms = u64::try_from(self.deadline.as_millis()).unwrap_or(u64::MAX);
65 if elapsed_ms >= deadline_ms {
66 return None;
67 }
68 Some(Duration::from_millis(deadline_ms - elapsed_ms))
69 }
70
71 pub fn deadline(&self) -> Duration {
73 self.deadline
74 }
75}
76
77pub fn transport_retry_backoff(policy: &TransportRetryPolicy, retry: u32) -> Duration {
79 let doublings = retry.saturating_sub(1).min(16);
80 policy
81 .initial_backoff
82 .saturating_mul(1u32 << doublings)
83 .min(policy.max_backoff)
84}