taskvisor/policies/
backoff.rs1use std::time::Duration;
33
34use crate::policies::jitter::JitterPolicy;
35
36#[derive(Clone, Copy, Debug)]
43pub struct BackoffPolicy {
44 pub first: Duration,
46 pub max: Duration,
48 pub factor: f64,
50 pub jitter: JitterPolicy,
52}
53
54impl Default for BackoffPolicy {
55 fn default() -> Self {
60 Self {
61 first: Duration::from_millis(100),
62 max: Duration::from_secs(30),
63 jitter: JitterPolicy::None,
64 factor: 1.0,
65 }
66 }
67}
68
69impl BackoffPolicy {
70 pub fn next(&self, prev: Option<Duration>) -> Duration {
80 let unclamped = match prev {
81 None => self.first,
82 Some(d) => {
83 let mul = d.as_secs_f64() * self.factor;
84 if !mul.is_finite() {
85 self.max
86 } else {
87 d.mul_f64(self.factor)
88 }
89 }
90 };
91
92 let base = if unclamped > self.max {
93 self.max
94 } else {
95 unclamped
96 };
97 match self.jitter {
98 JitterPolicy::Decorrelated => {
99 self.jitter
100 .apply_decorrelated(self.first.min(self.max), base, self.max)
101 }
102 _ => self.jitter.apply(base),
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use std::time::Duration;
111
112 #[test]
113 fn test_first_delay_no_jitter() {
114 let policy = BackoffPolicy {
115 first: Duration::from_millis(100),
116 max: Duration::from_secs(30),
117 factor: 2.0,
118 jitter: JitterPolicy::None,
119 };
120 assert_eq!(policy.next(None), Duration::from_millis(100));
121 }
122
123 #[test]
124 fn test_exponential_growth_no_jitter() {
125 let policy = BackoffPolicy {
126 first: Duration::from_millis(100),
127 max: Duration::from_secs(30),
128 factor: 2.0,
129 jitter: JitterPolicy::None,
130 };
131
132 let d1 = policy.next(None);
133 assert_eq!(d1, Duration::from_millis(100));
134
135 let d2 = policy.next(Some(d1));
136 assert_eq!(d2, Duration::from_millis(200));
137
138 let d3 = policy.next(Some(d2));
139 assert_eq!(d3, Duration::from_millis(400));
140
141 let d4 = policy.next(Some(d3));
142 assert_eq!(d4, Duration::from_millis(800));
143 }
144
145 #[test]
146 fn test_first_exceeds_max() {
147 let policy = BackoffPolicy {
148 first: Duration::from_secs(10),
149 max: Duration::from_secs(5),
150 factor: 2.0,
151 jitter: JitterPolicy::None,
152 };
153 let d1 = policy.next(None);
154 assert_eq!(d1, Duration::from_secs(5));
155 }
156
157 #[test]
158 fn test_monotonic_growth_with_equal_jitter() {
159 let policy = BackoffPolicy {
160 first: Duration::from_millis(100),
161 max: Duration::from_secs(30),
162 factor: 2.0,
163 jitter: JitterPolicy::Equal,
164 };
165
166 let mut prev = None;
167 let mut prev_delay = Duration::ZERO;
168
169 for i in 0..20 {
170 let delay = policy.next(prev);
171 if i > 5 {
172 assert!(
173 delay >= Duration::from_millis(10),
174 "iteration {}: delay {:?} is suspiciously low (prev: {:?})",
175 i,
176 delay,
177 prev_delay
178 );
179 }
180 prev_delay = delay;
181 prev = Some(delay);
182 }
183 }
184
185 #[test]
186 fn test_decorrelated_jitter_no_negative_feedback() {
187 let policy = BackoffPolicy {
188 first: Duration::from_millis(100),
189 max: Duration::from_secs(30),
190 factor: 2.0,
191 jitter: JitterPolicy::Decorrelated,
192 };
193
194 let mut prev = None;
195 let mut min_seen = Duration::from_secs(999);
196 let mut max_seen = Duration::ZERO;
197 for i in 0..100 {
198 let delay = policy.next(prev);
199
200 min_seen = min_seen.min(delay);
201 max_seen = max_seen.max(delay);
202
203 assert!(
204 delay >= Duration::from_millis(50), "iteration {}: delay {:?} too low (min_seen: {:?})",
206 i,
207 delay,
208 min_seen
209 );
210 if i > 10 {
211 assert!(
212 delay >= Duration::from_millis(200),
213 "iteration {}: delay {:?} suspiciously low after warmup",
214 i,
215 delay
216 );
217 }
218 prev = Some(delay);
219 }
220 println!("Decorrelated stats: min={:?}, max={:?}", min_seen, max_seen);
221 assert!(
222 max_seen > min_seen * 3,
223 "Range too narrow for decorrelated jitter"
224 );
225 }
226
227 #[test]
228 fn test_full_jitter_bounds() {
229 let policy = BackoffPolicy {
230 first: Duration::from_millis(1000),
231 max: Duration::from_secs(30),
232 factor: 1.0,
233 jitter: JitterPolicy::Full,
234 };
235 for _ in 0..50 {
236 let delay = policy.next(Some(Duration::from_millis(1000)));
237 assert!(delay <= Duration::from_millis(1000));
238 }
239 }
240
241 #[test]
242 fn test_equal_jitter_bounds() {
243 let policy = BackoffPolicy {
244 first: Duration::from_millis(1000),
245 max: Duration::from_secs(30),
246 factor: 1.0, jitter: JitterPolicy::Equal,
248 };
249 for _ in 0..50 {
250 let delay = policy.next(Some(Duration::from_millis(1000)));
251 assert!(delay >= Duration::from_millis(500));
252 assert!(delay <= Duration::from_millis(1000));
253 }
254 }
255}