1#![cfg_attr(not(feature = "std"), no_std)]
31#![warn(rust_2024_compatibility)]
32#![warn(clippy::all)]
33
34#[cfg(feature = "alloc")]
35extern crate alloc;
36
37pub mod backoff;
38#[cfg(feature = "std")]
39pub mod dsl;
40#[cfg(any(feature = "std", feature = "alloc"))]
41pub mod policy;
42pub mod retry;
43pub mod sleep;
44
45pub use backoff::{
46 fibonacci, BackoffPolicy, BackoffStrategy, ConstantBackoff, ExponentialBackoff,
47 FibonacciBackoff,
48};
49#[cfg(feature = "std")]
50pub use dsl::{builder_for_policy, retry_with_policy, DslError};
51#[cfg(any(feature = "std", feature = "alloc"))]
52pub use policy::PolicyRegistry;
53#[cfg(feature = "std")]
54pub use policy::{
55 clear_global_policies, get_global_policy, list_global_policies, register_global_policy,
56 remove_global_policy,
57};
58pub use retry::{RetryBuilder, RetryContext, RetryError, RetryOutcome, Retryable, RetryableExt};
59#[cfg(feature = "std")]
60pub use sleep::StdSleeper;
61pub use sleep::{FnSleeper, Sleeper};
62
63#[cfg(feature = "std")]
64use rand::rngs::StdRng;
65use rand::RngExt;
66
67use rand::Rng;
68
69#[derive(Debug, Clone, Copy)]
73pub struct Policy {
74 pub max_attempts: u8,
76
77 pub base_delay_ms: u64,
79
80 pub multiplier: f64,
82
83 pub max_delay_ms: u64,
85}
86
87impl Policy {
88 pub fn new() -> Self {
97 Self {
98 max_attempts: 3,
99 base_delay_ms: 100,
100 multiplier: 2.0,
101 max_delay_ms: 10_000,
102 }
103 }
104
105 #[cfg(feature = "std")]
134 pub fn calculate_delay(&self, attempt: u8, jitter_factor: f64) -> u64 {
135 let mut rng: StdRng = rand::make_rng();
136 self.calculate_delay_with_rng(attempt, jitter_factor, &mut rng)
137 }
138
139 pub fn calculate_delay_with_rng<R: Rng>(
157 &self,
158 attempt: u8,
159 jitter_factor: f64,
160 rng: &mut R,
161 ) -> u64 {
162 let mut jitter_factor = jitter_factor;
164 if jitter_factor.is_nan() {
165 jitter_factor = 1.0;
166 } else {
167 jitter_factor = jitter_factor.clamp(0.0, 1.0);
168 }
169
170 let exponent = attempt.saturating_sub(1) as i32;
172 let base_exponential = (self.base_delay_ms as f64) * self.multiplier.powi(exponent);
173
174 let capped = base_exponential.min(self.max_delay_ms as f64);
176
177 let random_scalar: f64 = rng.random_range(0.0..=1.0);
182 let jitter_blend = 1.0 - jitter_factor + random_scalar * jitter_factor;
183 let jittered = capped * jitter_blend;
184
185 jittered as u64
186 }
187
188 pub fn should_retry(&self, current_attempt: u8) -> bool {
198 current_attempt < self.max_attempts
199 }
200}
201
202impl Default for Policy {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use rand::rngs::StdRng;
212 use rand::SeedableRng;
213
214 #[test]
215 fn test_policy_default() {
216 let policy = Policy::default();
217 assert_eq!(policy.max_attempts, 3);
218 assert_eq!(policy.base_delay_ms, 100);
219 assert_eq!(policy.multiplier, 2.0);
220 assert_eq!(policy.max_delay_ms, 10_000);
221 }
222
223 #[test]
224 fn test_calculate_delay_bounds() {
225 let policy = Policy {
226 max_attempts: 5,
227 base_delay_ms: 100,
228 multiplier: 2.0,
229 max_delay_ms: 1000,
230 };
231
232 let mut rng = StdRng::seed_from_u64(42);
233
234 let delay1 = policy.calculate_delay_with_rng(1, 1.0, &mut rng);
236 assert!(delay1 <= 100);
237
238 let delay2 = policy.calculate_delay_with_rng(2, 1.0, &mut rng);
240 assert!(delay2 <= 200);
241
242 let delay5 = policy.calculate_delay_with_rng(5, 1.0, &mut rng);
244 assert!(delay5 <= 1000);
245 }
246
247 #[test]
248 fn test_should_retry() {
249 let policy = Policy {
250 max_attempts: 3,
251 ..Policy::default()
252 };
253
254 assert!(policy.should_retry(1));
255 assert!(policy.should_retry(2));
256 assert!(!policy.should_retry(3));
257 assert!(!policy.should_retry(4));
258 }
259
260 #[test]
261 fn test_max_delay_cap() {
262 let policy = Policy {
263 max_attempts: 10,
264 base_delay_ms: 100,
265 multiplier: 2.0,
266 max_delay_ms: 500,
267 };
268
269 let mut rng = StdRng::seed_from_u64(42);
270
271 let delay = policy.calculate_delay_with_rng(10, 1.0, &mut rng);
273 assert!(delay <= 500);
274 }
275
276 #[test]
277 fn test_zero_multiplier() {
278 let policy = Policy {
279 max_attempts: 5,
280 base_delay_ms: 100,
281 multiplier: 1.0, max_delay_ms: 10_000,
283 };
284
285 let mut rng = StdRng::seed_from_u64(42);
286
287 for attempt in 1..=5 {
289 let delay = policy.calculate_delay_with_rng(attempt, 1.0, &mut rng);
290 assert!(delay <= 100);
291 }
292 }
293
294 #[test]
295 fn test_jitter_factor() {
296 let policy = Policy {
297 max_attempts: 5,
298 base_delay_ms: 1000,
299 multiplier: 1.0,
300 max_delay_ms: 10_000,
301 };
302
303 let mut rng = StdRng::seed_from_u64(42);
304
305 let delay = policy.calculate_delay_with_rng(1, 0.1, &mut rng);
307 assert!(
308 delay >= 900 && delay <= 1000,
309 "delay {} not in range 900-1000",
310 delay
311 );
312
313 let delay = policy.calculate_delay_with_rng(1, 0.0, &mut rng);
315 assert_eq!(delay, 1000);
316
317 let delay = policy.calculate_delay_with_rng(1, 1.0, &mut rng);
319 assert!(delay <= 1000);
320 }
321
322 #[test]
323 fn test_jitter_factor_clamping() {
324 let policy = Policy {
325 max_attempts: 5,
326 base_delay_ms: 1000,
327 multiplier: 1.0,
328 max_delay_ms: 10_000,
329 };
330
331 let mut rng = StdRng::seed_from_u64(42);
332
333 let delay = policy.calculate_delay_with_rng(1, -0.5, &mut rng);
335 assert_eq!(delay, 1000, "negative jitter_factor should clamp to 0.0");
336
337 let delay = policy.calculate_delay_with_rng(1, 2.0, &mut rng);
339 assert!(
340 delay <= 1000,
341 "jitter_factor > 1.0 should clamp to 1.0, got delay {}",
342 delay
343 );
344
345 let delay = policy.calculate_delay_with_rng(1, 999.0, &mut rng);
347 assert!(delay <= 1000, "extreme jitter_factor should be clamped");
348
349 let delay = policy.calculate_delay_with_rng(1, -999.0, &mut rng);
350 assert_eq!(delay, 1000, "extreme negative should clamp to 0.0");
351 }
352}