Skip to main content

reliakit_retry/
policy.rs

1//! The retry policy: how many attempts, and the backoff schedule between them.
2
3use core::time::Duration;
4
5use reliakit_backoff::Backoff;
6
7/// How many times to attempt an operation and how long to wait between tries.
8///
9/// `max_attempts` counts the *total* number of attempts, including the first
10/// one, so `max_attempts = 1` means "try once, never retry" and
11/// `max_attempts = 3` means "the first try plus up to two retries". A value of
12/// `0` is rejected by [`new`](Self::new).
13///
14/// The [`Backoff`] supplies the delay *before each retry*. It is consulted only
15/// for delay values; the attempt count is governed solely by `max_attempts`, so
16/// the two limits never fight. If the backoff yields no delay for a given retry
17/// index, [`Duration::ZERO`] is used.
18#[derive(Debug, Clone, Copy)]
19pub struct RetryPolicy {
20    max_attempts: u32,
21    backoff: Backoff,
22}
23
24impl RetryPolicy {
25    /// Creates a policy that makes at most `max_attempts` attempts, waiting
26    /// according to `backoff` between them.
27    ///
28    /// Returns `None` if `max_attempts` is `0`, which would never run the
29    /// operation at all.
30    pub const fn new(max_attempts: u32, backoff: Backoff) -> Option<Self> {
31        if max_attempts == 0 {
32            return None;
33        }
34        Some(Self {
35            max_attempts,
36            backoff,
37        })
38    }
39
40    /// A policy that tries exactly once and never retries.
41    ///
42    /// Equivalent to `RetryPolicy::new(1, _).unwrap()`; the backoff is never
43    /// consulted because there is no retry.
44    pub const fn single(backoff: Backoff) -> Self {
45        Self {
46            max_attempts: 1,
47            backoff,
48        }
49    }
50
51    /// The maximum number of attempts (always `>= 1`).
52    pub const fn max_attempts(&self) -> u32 {
53        self.max_attempts
54    }
55
56    /// The backoff schedule used between attempts.
57    pub const fn backoff(&self) -> &Backoff {
58        &self.backoff
59    }
60
61    /// The delay to wait before the next retry, given how many attempts have
62    /// already completed.
63    ///
64    /// `completed_attempts` is the 1-based number of attempts already made, so
65    /// the delay before the first retry is `delay_before_retry(1)`. The backoff
66    /// is indexed zero-based (retry `0` is the first retry); if it yields no
67    /// delay, [`Duration::ZERO`] is returned.
68    pub fn delay_before_retry(&self, completed_attempts: u32) -> Duration {
69        let retry_index = completed_attempts.saturating_sub(1);
70        self.backoff.delay(retry_index).unwrap_or(Duration::ZERO)
71    }
72}