1use core::time::Duration;
2
3#[derive(Debug, Clone, Copy)]
5#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
6pub struct SseRetryConfig {
7 pub max_retries: u32,
9 pub max_backoff_ms: u32,
11 pub min_sleep_ms: u32,
13 pub backoff_multiplier: f32,
15 pub jitter: bool,
17}
18
19#[cfg(not(feature = "std"))]
20fn pown(mut x: f32, mut n: u32) -> f32 {
21 let mut out = 1.0;
22 while 0 < n {
23 if n & 1 != 0 {
24 out *= x;
25 }
26 x *= x;
27 n /= 2;
28 }
29 out
30}
31
32#[cfg(feature = "std")]
33fn pown(x: f32, n: u32) -> f32 {
34 x.powi(n.min(i32::MAX as _) as _)
35}
36
37impl SseRetryConfig {
38 #[inline]
53 #[must_use]
54 pub const fn new() -> Self {
55 Self {
56 max_retries: 20,
57 max_backoff_ms: 60_000,
58 min_sleep_ms: 200,
59 backoff_multiplier: 2.0,
60 jitter: true,
61 }
62 }
63
64 #[inline]
66 #[must_use]
67 pub const fn disabled() -> Self {
68 Self {
69 max_retries: 0,
70 ..Self::new()
71 }
72 }
73
74 #[must_use]
78 pub fn calculate_backoff_with_factor(
79 &self,
80 reconnect_time_ms: u32,
81 attempt: u32,
82 jitter_factor: f32,
83 ) -> Option<Duration> {
84 if self.max_retries <= attempt {
85 return None;
86 }
87
88 assert!(self.min_sleep_ms <= self.max_backoff_ms);
89
90 let reconnect_time_ms = reconnect_time_ms.max(self.min_sleep_ms) as f32;
91 let mut sleep_ms =
92 match self.backoff_multiplier.is_finite() && 1.0 <= self.backoff_multiplier {
93 true => reconnect_time_ms * pown(self.backoff_multiplier, attempt),
94 false => reconnect_time_ms,
95 };
96
97 if !sleep_ms.is_finite() || (self.max_backoff_ms as f32) <= sleep_ms {
98 sleep_ms = self.max_backoff_ms as _;
99 }
100
101 if self.jitter && reconnect_time_ms < sleep_ms {
102 let jitter_factor =
103 match jitter_factor.is_finite() && (0.0..=1.0).contains(&jitter_factor) {
104 true => jitter_factor,
105 false => 1.0,
106 };
107 sleep_ms = reconnect_time_ms + jitter_factor * (sleep_ms - reconnect_time_ms)
108 }
109
110 Some(Duration::from_millis(sleep_ms as _))
111 }
112
113 #[must_use]
117 #[cfg(feature = "fastrand")]
118 pub fn calculate_backoff(&self, reconnect_time_ms: u32, attempt: u32) -> Option<Duration> {
119 self.calculate_backoff_with_factor(reconnect_time_ms, attempt, fastrand::f32())
120 }
121}
122
123impl Default for SseRetryConfig {
124 fn default() -> Self {
125 Self::new()
126 }
127}