1#![cfg_attr(feature = "tokio", doc = "```rust")]
11#![cfg_attr(
12 not(feature = "tokio"),
13 doc = "```rust,ignore\n\
14 // Note: example not compiled if `tokio` feature is not enabled.\n"
15)]
16#![doc = include_str!("../examples/tokio-concurrent.rs")]
17#![doc = "```"]
19
20use crate::options::Options;
21use rand::Rng;
22use std::cmp;
23use std::time::{Duration, Instant};
24
25#[derive(Debug, Clone)]
27pub struct EaseOffCore {
28 options: Options,
29}
30
31#[derive(Debug, Clone, thiserror::Error)]
33#[error("{n}th retry is {:?} after deadline", retry_at.duration_since(*deadline))]
34pub struct RetryAfterDeadline {
35 pub n: u32,
37 pub retry_at: Instant,
39 pub deadline: Instant,
41}
42
43impl EaseOffCore {
44 #[inline(always)]
48 pub const fn new(options: Options) -> Self {
49 Self { options }
50 }
51
52 pub fn nth_retry_at(
58 &self,
59 n: u32,
60 now: Instant,
61 deadline: Option<Instant>,
62 rng: &mut (impl Rng + ?Sized),
63 ) -> Result<Option<Instant>, RetryAfterDeadline> {
64 let Options {
65 multiplier,
66 jitter,
67 initial_jitter,
68 initial_delay,
69 max_delay,
70 } = self.options;
71
72 let (delay, jitter) = if let Some(powi) = n.checked_sub(1) {
73 let delay = cmp::min(
74 duration_saturating_mul_f32(
75 initial_delay,
76 multiplier.powi(powi.try_into().unwrap_or(i32::MAX)),
77 ),
78 max_delay,
79 );
80
81 let jitter = get_jitter(delay, jitter, rng);
82
83 (delay, jitter)
84 } else {
85 #[allow(clippy::neg_cmp_op_on_partial_ord)]
87 if !(initial_jitter > 0f32) {
88 return Ok(None);
89 }
90
91 let jitter = get_jitter(initial_delay, initial_jitter, rng);
92 (initial_delay, jitter)
93 };
94
95 let retry_at = now + delay - jitter;
97
98 match deadline {
99 Some(deadline) if retry_at > deadline => Err(RetryAfterDeadline {
100 n,
101 retry_at,
102 deadline,
103 }),
104 _ => Ok(Some(retry_at)),
105 }
106 }
107}
108
109#[inline(always)]
111fn duration_saturating_mul_f32(duration: Duration, mul: f32) -> Duration {
112 Duration::try_from_secs_f32(duration.as_secs_f32() * mul).unwrap_or(Duration::MAX)
113}
114
115fn get_jitter(
116 base_duration: Duration,
117 jitter_factor: f32,
118 rng: &mut (impl Rng + ?Sized),
119) -> Duration {
120 let jitter_factor = if jitter_factor > 0f32 && jitter_factor < 1f32 {
121 jitter_factor * rng.gen::<f32>()
122 } else if jitter_factor >= 1f32 {
123 rng.gen::<f32>()
125 } else {
126 0f32
128 };
129
130 duration_saturating_mul_f32(base_duration, jitter_factor)
131}