1
1
use std:: time:: { Duration , Instant } ;
2
2
3
+ use rand:: { thread_rng, Rng } ;
4
+
3
5
pub ( crate ) trait RetryStrategy {
4
6
/// Return the next amount of time a failed request should delay before re-attempting.
5
7
fn next_delay ( & mut self , current_time : Instant ) -> Duration ;
@@ -17,18 +19,25 @@ pub(crate) struct BackoffRetry {
17
19
base_delay : Duration ,
18
20
max_delay : Duration ,
19
21
backoff_factor : u32 ,
22
+ include_jitter : bool ,
20
23
21
24
reset_interval : Duration ,
22
25
next_delay : Duration ,
23
26
good_since : Option < Instant > ,
24
27
}
25
28
26
29
impl BackoffRetry {
27
- pub fn new ( base_delay : Duration , max_delay : Duration , backoff_factor : u32 ) -> Self {
30
+ pub fn new (
31
+ base_delay : Duration ,
32
+ max_delay : Duration ,
33
+ backoff_factor : u32 ,
34
+ include_jitter : bool ,
35
+ ) -> Self {
28
36
Self {
29
37
base_delay,
30
38
max_delay,
31
39
backoff_factor,
40
+ include_jitter,
32
41
reset_interval : DEFAULT_RESET_RETRY_INTERVAL ,
33
42
next_delay : base_delay,
34
43
good_since : None ,
@@ -38,18 +47,22 @@ impl BackoffRetry {
38
47
39
48
impl RetryStrategy for BackoffRetry {
40
49
fn next_delay ( & mut self , current_time : Instant ) -> Duration {
41
- let mut next_delay = self . next_delay ;
50
+ let mut current_delay = self . next_delay ;
42
51
43
52
if let Some ( good_since) = self . good_since {
44
53
if current_time - good_since >= self . reset_interval {
45
- next_delay = self . base_delay ;
54
+ current_delay = self . base_delay ;
46
55
}
47
56
}
48
57
49
58
self . good_since = None ;
50
- self . next_delay = std:: cmp:: min ( self . max_delay , next_delay * self . backoff_factor ) ;
59
+ self . next_delay = std:: cmp:: min ( self . max_delay , current_delay * self . backoff_factor ) ;
51
60
52
- next_delay
61
+ if self . include_jitter {
62
+ thread_rng ( ) . gen_range ( current_delay / 2 ..=current_delay)
63
+ } else {
64
+ current_delay
65
+ }
53
66
}
54
67
55
68
fn change_base_delay ( & mut self , base_delay : Duration ) {
@@ -76,7 +89,7 @@ mod tests {
76
89
#[ test]
77
90
fn test_fixed_retry ( ) {
78
91
let base = Duration :: from_secs ( 10 ) ;
79
- let mut retry = BackoffRetry :: new ( base, Duration :: from_secs ( 30 ) , 1 ) ;
92
+ let mut retry = BackoffRetry :: new ( base, Duration :: from_secs ( 30 ) , 1 , false ) ;
80
93
let start = Instant :: now ( ) - Duration :: from_secs ( 60 ) ;
81
94
82
95
assert_eq ! ( retry. next_delay( start) , base) ;
@@ -87,7 +100,7 @@ mod tests {
87
100
#[ test]
88
101
fn test_able_to_reset_base_delay ( ) {
89
102
let base = Duration :: from_secs ( 10 ) ;
90
- let mut retry = BackoffRetry :: new ( base, Duration :: from_secs ( 30 ) , 1 ) ;
103
+ let mut retry = BackoffRetry :: new ( base, Duration :: from_secs ( 30 ) , 1 , false ) ;
91
104
let start = Instant :: now ( ) ;
92
105
93
106
assert_eq ! ( retry. next_delay( start) , base) ;
@@ -102,7 +115,7 @@ mod tests {
102
115
fn test_with_backoff ( ) {
103
116
let base = Duration :: from_secs ( 10 ) ;
104
117
let max = Duration :: from_secs ( 60 ) ;
105
- let mut retry = BackoffRetry :: new ( base, max, 2 ) ;
118
+ let mut retry = BackoffRetry :: new ( base, max, 2 , false ) ;
106
119
let start = Instant :: now ( ) - Duration :: from_secs ( 60 ) ;
107
120
108
121
assert_eq ! ( retry. next_delay( start) , base) ;
@@ -117,12 +130,23 @@ mod tests {
117
130
assert_eq ! ( retry. next_delay( start. add( Duration :: from_secs( 3 ) ) ) , max) ;
118
131
}
119
132
133
+ #[ test]
134
+ fn test_with_jitter ( ) {
135
+ let base = Duration :: from_secs ( 10 ) ;
136
+ let max = Duration :: from_secs ( 60 ) ;
137
+ let mut retry = BackoffRetry :: new ( base, max, 1 , true ) ;
138
+ let start = Instant :: now ( ) - Duration :: from_secs ( 60 ) ;
139
+
140
+ let delay = retry. next_delay ( start) ;
141
+ assert ! ( base / 2 <= delay && delay <= base) ;
142
+ }
143
+
120
144
#[ test]
121
145
fn test_retry_holds_at_max ( ) {
122
146
let base = Duration :: from_secs ( 20 ) ;
123
147
let max = Duration :: from_secs ( 30 ) ;
124
148
125
- let mut retry = BackoffRetry :: new ( base, max, 2 ) ;
149
+ let mut retry = BackoffRetry :: new ( base, max, 2 , false ) ;
126
150
let start = Instant :: now ( ) ;
127
151
retry. reset ( start) ;
128
152
@@ -146,7 +170,7 @@ mod tests {
146
170
let reset_interval = Duration :: from_secs ( 45 ) ;
147
171
148
172
// Prepare a retry strategy that has succeeded at a specific point.
149
- let mut retry = BackoffRetry :: new ( base, max, 2 ) ;
173
+ let mut retry = BackoffRetry :: new ( base, max, 2 , false ) ;
150
174
retry. reset_interval = reset_interval;
151
175
let start = Instant :: now ( ) - Duration :: from_secs ( 60 ) ;
152
176
retry. reset ( start) ;
0 commit comments