Skip to content

Commit f8d0143

Browse files
YaroShkvoretslutter
authored andcommitted
graph: fix backoff overflow
1 parent e1f04cf commit f8d0143

File tree

1 file changed

+69
-1
lines changed

1 file changed

+69
-1
lines changed

graph/src/util/backoff.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl ExponentialBackoff {
3333
}
3434

3535
pub fn delay(&self) -> Duration {
36-
let mut delay = self.base.saturating_mul(1 << self.attempt);
36+
let mut delay = self.base.saturating_mul(1u32 << self.attempt.min(31));
3737
if delay > self.ceiling {
3838
delay = self.ceiling;
3939
}
@@ -50,3 +50,71 @@ impl ExponentialBackoff {
5050
self.attempt = 0;
5151
}
5252
}
53+
54+
#[cfg(test)]
55+
mod tests {
56+
use super::*;
57+
use std::time::Instant;
58+
59+
#[test]
60+
fn test_delay() {
61+
let mut backoff =
62+
ExponentialBackoff::new(Duration::from_millis(500), Duration::from_secs(5));
63+
64+
// First delay should be base (0.5s)
65+
assert_eq!(backoff.next_attempt(), Duration::from_millis(500));
66+
67+
// Second delay should be 1s (base * 2^1)
68+
assert_eq!(backoff.next_attempt(), Duration::from_secs(1));
69+
70+
// Third delay should be 2s (base * 2^2)
71+
assert_eq!(backoff.next_attempt(), Duration::from_secs(2));
72+
73+
// Fourth delay should be 4s (base * 2^3)
74+
assert_eq!(backoff.next_attempt(), Duration::from_secs(4));
75+
76+
// Seventh delay should be ceiling (5s)
77+
assert_eq!(backoff.next_attempt(), Duration::from_secs(5));
78+
79+
// Eighth delay should also be ceiling (5s)
80+
assert_eq!(backoff.next_attempt(), Duration::from_secs(5));
81+
}
82+
83+
#[test]
84+
fn test_overflow_delay() {
85+
let mut backoff =
86+
ExponentialBackoff::new(Duration::from_millis(500), Duration::from_secs(45));
87+
88+
// 31st should be ceiling (45s) without overflowing
89+
backoff.attempt = 31;
90+
assert_eq!(backoff.next_attempt(), Duration::from_secs(45));
91+
assert_eq!(backoff.next_attempt(), Duration::from_secs(45));
92+
93+
backoff.attempt = 123456;
94+
assert_eq!(backoff.next_attempt(), Duration::from_secs(45));
95+
}
96+
97+
#[tokio::test]
98+
async fn test_sleep_async() {
99+
let mut backoff =
100+
ExponentialBackoff::new(Duration::from_secs_f32(0.1), Duration::from_secs_f32(0.2));
101+
102+
let start = Instant::now();
103+
backoff.sleep_async().await;
104+
let elapsed = start.elapsed();
105+
106+
assert!(elapsed >= Duration::from_secs_f32(0.1) && elapsed < Duration::from_secs_f32(0.15));
107+
108+
let start = Instant::now();
109+
backoff.sleep_async().await;
110+
let elapsed = start.elapsed();
111+
112+
assert!(elapsed >= Duration::from_secs_f32(0.2) && elapsed < Duration::from_secs_f32(0.25));
113+
114+
let start = Instant::now();
115+
backoff.sleep_async().await;
116+
let elapsed = start.elapsed();
117+
118+
assert!(elapsed >= Duration::from_secs_f32(0.2) && elapsed < Duration::from_secs_f32(0.25));
119+
}
120+
}

0 commit comments

Comments
 (0)