Skip to main content

cloud_sdk/action_polling/
backoff.rs

1use crate::retry::MonotonicDuration;
2
3use super::{PollContext, ProgressChange};
4
5/// Largest admitted exponential multiplier.
6pub const MAX_BACKOFF_MULTIPLIER: u8 = 16;
7
8/// Caller-owned action polling backoff without sleep or clock access.
9pub trait PollBackoff {
10    /// Policy-specific failure. Driver diagnostics always redact this value.
11    type Error;
12
13    /// Chooses one requested delay from validated nonsensitive context.
14    fn delay(&mut self, context: PollContext) -> Result<MonotonicDuration, Self::Error>;
15}
16
17/// Invalid exponential backoff configuration.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum ExponentialBackoffError {
20    /// Initial and maximum delays must be nonzero.
21    ZeroDelay,
22    /// Initial delay exceeds the maximum.
23    InitialExceedsMaximum,
24    /// Multiplier is zero or exceeds its hard bound.
25    InvalidMultiplier,
26}
27
28impl_static_error!(ExponentialBackoffError,
29    Self::ZeroDelay => "poll backoff delays must be nonzero",
30    Self::InitialExceedsMaximum => "initial poll backoff exceeds its maximum",
31    Self::InvalidMultiplier => "poll backoff multiplier is invalid",
32);
33
34/// Allocation-free exponential backoff reset by provider progress.
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub struct ExponentialBackoff {
37    initial: MonotonicDuration,
38    maximum: MonotonicDuration,
39    multiplier: u8,
40    next: MonotonicDuration,
41}
42
43impl ExponentialBackoff {
44    /// Creates bounded deterministic backoff.
45    pub const fn new(
46        initial: MonotonicDuration,
47        maximum: MonotonicDuration,
48        multiplier: u8,
49    ) -> Result<Self, ExponentialBackoffError> {
50        if initial.get() == 0 || maximum.get() == 0 {
51            return Err(ExponentialBackoffError::ZeroDelay);
52        }
53        if initial.get() > maximum.get() {
54            return Err(ExponentialBackoffError::InitialExceedsMaximum);
55        }
56        if multiplier == 0 || multiplier > MAX_BACKOFF_MULTIPLIER {
57            return Err(ExponentialBackoffError::InvalidMultiplier);
58        }
59        Ok(Self {
60            initial,
61            maximum,
62            multiplier,
63            next: initial,
64        })
65    }
66
67    /// Returns the next delay before progress policy is applied.
68    #[must_use]
69    pub const fn next_delay(self) -> MonotonicDuration {
70        self.next
71    }
72}
73
74impl PollBackoff for ExponentialBackoff {
75    type Error = core::convert::Infallible;
76
77    fn delay(&mut self, context: PollContext) -> Result<MonotonicDuration, Self::Error> {
78        if matches!(
79            context.progress_change(),
80            ProgressChange::Initial | ProgressChange::Advanced | ProgressChange::Reset
81        ) {
82            self.next = self.initial;
83        }
84        let selected = self.next;
85        let multiplied = selected
86            .get()
87            .saturating_mul(u64::from(self.multiplier))
88            .min(self.maximum.get());
89        self.next = MonotonicDuration::new(multiplied);
90        Ok(selected)
91    }
92}