lance_core/utils/
backoff.rs1use rand::{Rng, SeedableRng};
2use std::time::Duration;
3
4pub struct Backoff {
18 base: u32,
19 unit: u32,
20 jitter: i32,
21 min: u32,
22 max: u32,
23 attempt: u32,
24}
25
26impl Default for Backoff {
27 fn default() -> Self {
28 Self {
29 base: 2,
30 unit: 50,
31 jitter: 50,
32 min: 0,
33 max: 5000,
34 attempt: 0,
35 }
36 }
37}
38
39impl Backoff {
40 pub fn with_base(self, base: u32) -> Self {
41 Self { base, ..self }
42 }
43
44 pub fn with_unit(self, unit: u32) -> Self {
45 Self { unit, ..self }
46 }
47
48 pub fn with_jitter(self, jitter: i32) -> Self {
49 Self { jitter, ..self }
50 }
51
52 pub fn with_min(self, min: u32) -> Self {
53 Self { min, ..self }
54 }
55
56 pub fn with_max(self, max: u32) -> Self {
57 Self { max, ..self }
58 }
59
60 pub fn next_backoff(&mut self) -> Duration {
61 let backoff = self
62 .base
63 .saturating_pow(self.attempt)
64 .saturating_mul(self.unit);
65 let jitter = rand::rng().random_range(-self.jitter..=self.jitter);
66 let backoff = (backoff.saturating_add_signed(jitter)).clamp(self.min, self.max);
67 self.attempt += 1;
68 Duration::from_millis(backoff as u64)
69 }
70
71 pub fn attempt(&self) -> u32 {
72 self.attempt
73 }
74
75 pub fn reset(&mut self) {
76 self.attempt = 0;
77 }
78}
79
80pub struct SlotBackoff {
112 base: u32,
113 unit: u32,
114 starting_i: u32,
115 attempt: u32,
116 rng: rand::rngs::SmallRng,
117}
118
119impl Default for SlotBackoff {
120 fn default() -> Self {
121 Self {
122 base: 2,
123 unit: 50,
124 starting_i: 2, attempt: 0,
126 rng: rand::rngs::SmallRng::from_os_rng(),
127 }
128 }
129}
130
131impl SlotBackoff {
132 pub fn with_unit(self, unit: u32) -> Self {
133 Self { unit, ..self }
134 }
135
136 pub fn attempt(&self) -> u32 {
137 self.attempt
138 }
139
140 pub fn next_backoff(&mut self) -> Duration {
141 let num_slots = self.base.saturating_pow(self.attempt + self.starting_i);
142 let slot_i = self.rng.random_range(0..num_slots);
143 self.attempt += 1;
144 Duration::from_millis((slot_i * self.unit) as u64)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_backoff() {
154 let mut backoff = Backoff::default().with_jitter(0);
155 assert_eq!(backoff.next_backoff().as_millis(), 50);
156 assert_eq!(backoff.attempt(), 1);
157 assert_eq!(backoff.next_backoff().as_millis(), 100);
158 assert_eq!(backoff.attempt(), 2);
159 assert_eq!(backoff.next_backoff().as_millis(), 200);
160 assert_eq!(backoff.attempt(), 3);
161 assert_eq!(backoff.next_backoff().as_millis(), 400);
162 assert_eq!(backoff.attempt(), 4);
163 }
164
165 #[test]
166 fn test_backoff_with_base() {
167 let mut backoff = Backoff::default().with_base(3).with_jitter(0);
168 assert_eq!(backoff.next_backoff().as_millis(), 50); assert_eq!(backoff.next_backoff().as_millis(), 150); assert_eq!(backoff.next_backoff().as_millis(), 450); }
172
173 #[test]
174 fn test_backoff_with_unit() {
175 let mut backoff = Backoff::default().with_unit(100).with_jitter(0);
176 assert_eq!(backoff.next_backoff().as_millis(), 100); assert_eq!(backoff.next_backoff().as_millis(), 200); }
179
180 #[test]
181 fn test_backoff_with_min() {
182 let mut backoff = Backoff::default().with_min(100).with_jitter(0);
183 assert_eq!(backoff.next_backoff().as_millis(), 100); }
185
186 #[test]
187 fn test_backoff_with_max() {
188 let mut backoff = Backoff::default().with_max(75).with_jitter(0);
189 assert_eq!(backoff.next_backoff().as_millis(), 50);
190 assert_eq!(backoff.next_backoff().as_millis(), 75); }
192
193 #[test]
194 fn test_backoff_reset() {
195 let mut backoff = Backoff::default().with_jitter(0);
196 assert_eq!(backoff.next_backoff().as_millis(), 50);
197 assert_eq!(backoff.attempt(), 1);
198 backoff.reset();
199 assert_eq!(backoff.attempt(), 0);
200 assert_eq!(backoff.next_backoff().as_millis(), 50);
201 }
202
203 #[test]
204 fn test_slot_backoff() {
205 #[cfg_attr(coverage, coverage(off))]
206 fn assert_in(value: u128, expected: &[u128]) {
207 assert!(
208 expected.contains(&value),
209 "value {} not in {:?}",
210 value,
211 expected
212 );
213 }
214
215 for _ in 0..10 {
216 let mut backoff = SlotBackoff::default().with_unit(100);
217 assert_in(backoff.next_backoff().as_millis(), &[0, 100, 200, 300]);
218 assert_eq!(backoff.attempt(), 1);
219 assert_in(
220 backoff.next_backoff().as_millis(),
221 &[0, 100, 200, 300, 400, 500, 600, 700],
222 );
223 assert_eq!(backoff.attempt(), 2);
224 assert_in(
225 backoff.next_backoff().as_millis(),
226 &(0..16).map(|i| i * 100).collect::<Vec<_>>(),
227 );
228 assert_eq!(backoff.attempt(), 3);
229 }
230 }
231}