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_slot_backoff() {
167 fn assert_in(value: u128, expected: &[u128]) {
168 assert!(
169 expected.contains(&value),
170 "value {} not in {:?}",
171 value,
172 expected
173 );
174 }
175
176 for _ in 0..10 {
177 let mut backoff = SlotBackoff::default().with_unit(100);
178 assert_in(backoff.next_backoff().as_millis(), &[0, 100, 200, 300]);
179 assert_eq!(backoff.attempt(), 1);
180 assert_in(
181 backoff.next_backoff().as_millis(),
182 &[0, 100, 200, 300, 400, 500, 600, 700],
183 );
184 assert_eq!(backoff.attempt(), 2);
185 assert_in(
186 backoff.next_backoff().as_millis(),
187 &(0..16).map(|i| i * 100).collect::<Vec<_>>(),
188 );
189 assert_eq!(backoff.attempt(), 3);
190 }
191 }
192}