ease_off/options.rs
1use crate::core::EaseOffCore;
2use crate::EaseOff;
3use std::num::Saturating;
4use std::time::{Duration, Instant};
5
6/// Configuration options for [`EaseOff`] and [`EaseOffCore`].
7///
8/// Designed to be stored in a `const` or `static`:
9///
10/// ```rust
11/// use std::time::Duration;
12///
13/// const BACKOFF_OPTS: ease_off::Options = ease_off::Options::new()
14/// .initial_jitter(0.25)
15/// .initial_delay(Duration::from_secs(1))
16/// .max_delay(Duration::from_secs(5 * 60)); // 5 minutes
17/// ```
18#[derive(Debug, Clone)]
19pub struct Options {
20 pub(crate) multiplier: f32,
21 pub(crate) jitter: f32,
22 pub(crate) initial_jitter: f32,
23 pub(crate) initial_delay: Duration,
24 pub(crate) max_delay: Duration,
25}
26
27impl Options {
28 /// Default ease-off options which should be suitable for most applications.
29 ///
30 /// See source for current values.
31 pub const DEFAULT: Options = Options {
32 multiplier: 2.0,
33 jitter: 0.25,
34 initial_jitter: 0.0,
35 initial_delay: Duration::from_millis(150),
36 max_delay: Duration::from_secs(60), // one minute
37 };
38
39 /// Returns [`Self::DEFAULT`].
40 #[inline(always)]
41 pub const fn new() -> Self {
42 Self::DEFAULT
43 }
44
45 /// Set the factor to multiply the next delay by.
46 ///
47 /// * If `> 1`, backoff is exponential.
48 /// * If `== 1`, backoff is constant before [jitter][Self::jitter].
49 /// * If `< 1`, backoff is logarithmic(?). In any case, not recommended.
50 ///
51 /// Any multiplication that results in an invalid value for [`Duration`] saturates
52 /// to [`Duration::MAX`] or [`max_delay`][Self::max_delay], whichever is lower.
53 #[inline(always)]
54 pub const fn multiplier(self, multiplier: f32) -> Self {
55 Self { multiplier, ..self }
56 }
57
58 /// Get the factor that the next delay will be multiplied by.
59 #[inline(always)]
60 pub const fn get_multiplier(&self) -> f32 {
61 self.multiplier
62 }
63
64 /// Set the maximum jitter factor.
65 ///
66 /// The next backoff delay will be multiplied by a random factor in the range `(1 - jitter, 1]`.
67 ///
68 /// This helps prevent a situation where attempts line up from multiple processes
69 /// following the same backoff algorithm, which would constitute a [thundering herd].
70 ///
71 /// This value is clamped to the interval `[0, 1]` when calculating the next delay.
72 ///
73 /// If `jitter` is `<= 0` or `NaN`, no random jitter is applied (not recommended for most cases).
74 ///
75 /// If `jitter >= 1`, the next delay can be anywhere between `[0, next_delay]`,
76 /// which means the next attempt _could_ happen immediately, without waiting.
77 ///
78 /// [thundering herd]: https://en.wikipedia.org/wiki/Thundering_herd_problem
79 #[inline(always)]
80 pub const fn jitter(self, jitter: f32) -> Self {
81 Self { jitter, ..self }
82 }
83
84 /// Get the maximum jitter factor.
85 ///
86 /// See [`Self::jitter()`] for details.
87 #[inline(always)]
88 pub const fn get_jitter(&self) -> f32 {
89 self.jitter
90 }
91
92 /// Set the jitter factor used to delay the first attempt.
93 ///
94 /// The initial wait before the first attempt will be [`initial_delay`][Self::initial_delay]
95 /// multiplied by a random factor in the range `(1 - initial_jitter, 1]`.
96 ///
97 /// This mitigates the [thundering herd problem] when multiple processes start up
98 /// at the same time and all try to access the same resource.
99 ///
100 /// This value is clamped to the interval `[0, 1]` when calculating the initial delay.
101 ///
102 /// If `initial_jitter` is `<= 0` or `NaN`, the first attempt occurs immediately.
103 ///
104 /// The delay after the first failure will be calculated as normal;
105 /// [`multiplier`][Self::multiplier] is _not_ applied until after the first retryable failure.
106 ///
107 /// [thundering herd problem]: https://en.wikipedia.org/wiki/Thundering_herd_problem
108 #[inline(always)]
109 pub const fn initial_jitter(self, initial_jitter: f32) -> Self {
110 Self {
111 initial_jitter,
112 ..self
113 }
114 }
115
116 /// Get the jitter factor used for the _first_ attempt.
117 ///
118 /// See [`Self::initial_jitter()`] for details.
119 #[inline(always)]
120 pub const fn get_initial_jitter(&self) -> f32 {
121 self.initial_jitter
122 }
123
124 /// Set the delay for the first backoff attempt.
125 #[inline(always)]
126 pub const fn initial_delay(self, initial_delay: Duration) -> Self {
127 Self {
128 initial_delay,
129 ..self
130 }
131 }
132
133 /// Get the delay for the first backoff attempt.
134 ///
135 /// See [`Self::initial_delay()`] for details.
136 #[inline(always)]
137 pub const fn get_initial_delay(&self) -> Duration {
138 self.initial_delay
139 }
140
141 /// Set the maximum delay to wait between backoff attempts.
142 #[inline(always)]
143 pub const fn max_delay(self, max_delay: Duration) -> Self {
144 Self { max_delay, ..self }
145 }
146
147 /// Get the maximum delay to wait between backoff attempts.
148 ///
149 /// See [`Self::max_delay()`] for details.
150 #[inline(always)]
151 pub const fn get_max_delay(&self) -> Duration {
152 self.max_delay
153 }
154
155 /// Convert this `Options` into an [`EaseOffCore`].
156 #[inline(always)]
157 pub const fn into_core(self) -> EaseOffCore {
158 EaseOffCore::new(self)
159 }
160}
161
162/// Methods to create an [`EaseOff`].
163impl Options {
164 /// Begin backing off with **indefinite** retries.
165 ///
166 /// The operation will be retried until it succeeds, or a non-retryable error occurs.
167 pub fn start_unlimited<E>(&self) -> EaseOff<E> {
168 self.start(Instant::now(), None)
169 }
170
171 /// Begin backing off, limited by the given timeout.
172 ///
173 /// Always makes one attempt, even if the timeout is zero or has elapsed
174 /// by the time the first attempt is made.
175 ///
176 /// See also:
177 /// * [`Self::start_timeout_opt()`] for a conditional timeout.
178 /// * [`Self::start_deadline()`] to specify an [`Instant`] as a deadline.
179 pub fn start_timeout<E>(&self, timeout: Duration) -> EaseOff<E> {
180 let started_at = Instant::now();
181 self.start(started_at, started_at.checked_add(timeout))
182 }
183
184 /// Begin backing off, limited by the given optional timeout.
185 ///
186 /// If `timeout` is `None`, this is equivalent to [`Self::start_unlimited()`].
187 ///
188 /// Always makes one attempt, even if the timeout is zero or has elapsed
189 /// by the time the first attempt is made.
190 ///
191 /// See also:
192 /// * [`Self::start_timeout()`] for a non-conditional timeout.
193 /// * [`Self::start_deadline_opt()`] to specify an optional [`Instant`] as a deadline.
194 pub fn start_timeout_opt<E>(&self, timeout: Option<Duration>) -> EaseOff<E> {
195 let started_at = Instant::now();
196 self.start(
197 started_at,
198 timeout.and_then(|timeout| started_at.checked_add(timeout)),
199 )
200 }
201
202 /// Begin backing off, halting attempts at the given deadline.
203 ///
204 /// Always makes one attempt, even if the deadline is `<= Instant::now()` or has elapsed
205 /// by the time the first attempt is made.
206 ///
207 /// See also:
208 /// * [`Self::start_deadline_opt()`] for a conditional deadline.
209 /// * [`Self::start_timeout()`] to specify a [`Duration`] as a timeout.
210 pub fn start_deadline<E>(&self, deadline: Instant) -> EaseOff<E> {
211 self.start(Instant::now(), Some(deadline))
212 }
213
214 /// Begin backing off, halting attempts at the given deadline.
215 ///
216 /// If `deadline` is `None`, this is equivalent to [`Self::start_unlimited()`].
217 ///
218 /// Always makes one attempt, even if the deadline is `<= Instant::now()` or has elapsed
219 /// by the time the first attempt is made.
220 ///
221 /// See also:
222 /// * [`Self::start_deadline()`] for a non-conditional deadline.
223 /// * [`Self::start_timeout_opt()`] to specify an optional [`Duration`] as a timeout.
224 pub fn start_deadline_opt<E>(&self, deadline: Option<Instant>) -> EaseOff<E> {
225 self.start(Instant::now(), deadline)
226 }
227
228 fn start<E>(&self, started_at: Instant, deadline: Option<Instant>) -> EaseOff<E> {
229 EaseOff {
230 core: EaseOffCore::new(self.clone()),
231 started_at,
232 deadline,
233 num_attempts: Saturating(0),
234 last_error: None,
235 next_retry_at: None,
236 }
237 }
238}
239
240impl Default for Options {
241 /// Returns [`Self::DEFAULT`].
242 #[inline(always)]
243 fn default() -> Self {
244 Self::DEFAULT
245 }
246}