2
2
//!
3
3
//! # Usage
4
4
//!
5
- //! Retry an operation using the `retry` function. `retry` accepts an iterator over `Duration`s and
6
- //! a closure that returns a `Result`. The iterator is used to determine how long to wait after
7
- //! each unsuccessful try and how many times to try before giving up and returning `Result::Err`.
5
+ //! Retry an operation using the `retry` function. `retry` accepts an iterator
6
+ //! over `Duration`s and a closure that returns a `retry::OperationResult`. The
7
+ //! iterator is used to determine how long to wait after each unsuccessful try
8
+ //! and how many times to try before giving up and returning `Result::Err`. The
9
+ //! closure is used to determine either the value to return, or whether to retry
10
+ //! on non-fatal errors, or to immediately stop on fatal errors.
8
11
//!
9
12
//! Any type that implements `Iterator<Duration>` can be used to determine retry behavior, though a
10
13
//! few useful implementations are provided in the `delay` module, including a fixed delay and
11
14
//! exponential back-off.
12
15
//!
16
+ //! `retry::OperationResult` implements `From` for `std::result::Result`, so for
17
+ //! the reasonably common case where no fatal errors are expected, just use
18
+ //! `.into()` to cast into the required type - `Result::Err` becomes
19
+ //! `OperationResult::Retry`.
20
+ //!
13
21
//! ```
14
22
//! # use retry::retry;
15
23
//! # use retry::delay::Fixed;
20
28
//! Some(n) if n == 3 => Ok("n is 3!"),
21
29
//! Some(_) => Err("n must be 3!"),
22
30
//! None => Err("n was never 3!"),
23
- //! }
31
+ //! }.into()
24
32
//! });
25
33
//!
26
34
//! assert!(result.is_ok());
39
47
//! Some(n) if n == 3 => Ok("n is 3!"),
40
48
//! Some(_) => Err("n must be 3!"),
41
49
//! None => Err("n was never 3!"),
42
- //! }
50
+ //! }.into()
43
51
//! });
44
52
//!
45
53
//! assert!(result.is_err());
58
66
//! Some(n) if n == 3 => Ok("n is 3!"),
59
67
//! Some(_) => Err("n must be 3!"),
60
68
//! None => Err("n was never 3!"),
61
- //! }
69
+ //! }.into()
62
70
//! });
63
71
//!
64
72
//! assert!(result.is_ok());
65
73
//! ```
74
+ //!
75
+ //! To deal with fatal errors, return `OperationResult` directlythread::spawn(move || {
76
+ //!
77
+ //! ```
78
+ //! # use retry::retry;
79
+ //! # use retry::delay::Fixed;
80
+ //! use retry::OperationResult;
81
+ //! let mut collection = vec![1, 2].into_iter();
82
+ //! let value = retry(Fixed::from_millis(1), || {
83
+ //! match collection.next() {
84
+ //! Some(n) if n == 2 => OperationResult::Ok(n),
85
+ //! Some(_) => OperationResult::Retry("not 2"),
86
+ //! None => OperationResult::Err("not found"),
87
+ //! }
88
+ //! }).unwrap();
89
+ //!
90
+ //! assert_eq!(value, 2);
91
+ //! ```
66
92
67
93
#![ deny( missing_debug_implementations, missing_docs, warnings) ]
68
94
@@ -74,22 +100,24 @@ use std::{
74
100
} ;
75
101
76
102
pub mod delay;
103
+ pub mod opresult;
104
+
105
+ pub use opresult:: OperationResult ;
77
106
78
107
/// Retry the given operation synchronously until it succeeds, or until the given `Duration`
79
- /// iterator ends.
80
108
pub fn retry < I , O , R , E > ( iterable : I , mut operation : O ) -> Result < R , Error < E > >
81
109
where
82
110
I : IntoIterator < Item = Duration > ,
83
- O : FnMut ( ) -> Result < R , E > ,
111
+ O : FnMut ( ) -> OperationResult < R , E > ,
84
112
{
85
113
let mut iterator = iterable. into_iter ( ) ;
86
114
let mut current_try = 1 ;
87
115
let mut total_delay = Duration :: default ( ) ;
88
116
89
117
loop {
90
118
match operation ( ) {
91
- Ok ( value) => return Ok ( value) ,
92
- Err ( error) => {
119
+ OperationResult :: Ok ( value) => return Ok ( value) ,
120
+ OperationResult :: Retry ( error) => {
93
121
if let Some ( delay) = iterator. next ( ) {
94
122
sleep ( delay) ;
95
123
current_try += 1 ;
@@ -102,6 +130,13 @@ where
102
130
} ) ;
103
131
}
104
132
}
133
+ OperationResult :: Err ( error) => {
134
+ return Err ( Error :: Operation {
135
+ error : error,
136
+ total_delay : total_delay,
137
+ tries : current_try,
138
+ } ) ;
139
+ }
105
140
}
106
141
}
107
142
}
@@ -158,16 +193,20 @@ mod tests {
158
193
use std:: time:: Duration ;
159
194
160
195
use super :: delay:: { Exponential , Fixed , NoDelay , Range } ;
196
+ use super :: opresult:: OperationResult ;
161
197
use super :: { retry, Error } ;
162
198
163
199
#[ test]
164
200
fn succeeds_with_infinite_retries ( ) {
165
201
let mut collection = vec ! [ 1 , 2 , 3 , 4 , 5 ] . into_iter ( ) ;
166
202
167
- let value = retry ( NoDelay , || match collection. next ( ) {
168
- Some ( n) if n == 5 => Ok ( n) ,
169
- Some ( _) => Err ( "not 5" ) ,
170
- None => Err ( "not 5" ) ,
203
+ let value = retry ( NoDelay , || {
204
+ match collection. next ( ) {
205
+ Some ( n) if n == 5 => Ok ( n) ,
206
+ Some ( _) => Err ( "not 5" ) ,
207
+ None => Err ( "not 5" ) ,
208
+ }
209
+ . into ( )
171
210
} )
172
211
. unwrap ( ) ;
173
212
@@ -178,10 +217,13 @@ mod tests {
178
217
fn succeeds_with_maximum_retries ( ) {
179
218
let mut collection = vec ! [ 1 , 2 ] . into_iter ( ) ;
180
219
181
- let value = retry ( NoDelay . take ( 1 ) , || match collection. next ( ) {
182
- Some ( n) if n == 2 => Ok ( n) ,
183
- Some ( _) => Err ( "not 2" ) ,
184
- None => Err ( "not 2" ) ,
220
+ let value = retry ( NoDelay . take ( 1 ) , || {
221
+ match collection. next ( ) {
222
+ Some ( n) if n == 2 => Ok ( n) ,
223
+ Some ( _) => Err ( "not 2" ) ,
224
+ None => Err ( "not 2" ) ,
225
+ }
226
+ . into ( )
185
227
} )
186
228
. unwrap ( ) ;
187
229
@@ -192,10 +234,13 @@ mod tests {
192
234
fn fails_after_last_try ( ) {
193
235
let mut collection = vec ! [ 1 ] . into_iter ( ) ;
194
236
195
- let res = retry ( NoDelay . take ( 1 ) , || match collection. next ( ) {
196
- Some ( n) if n == 2 => Ok ( n) ,
197
- Some ( _) => Err ( "not 2" ) ,
198
- None => Err ( "not 2" ) ,
237
+ let res = retry ( NoDelay . take ( 1 ) , || {
238
+ match collection. next ( ) {
239
+ Some ( n) if n == 2 => Ok ( n) ,
240
+ Some ( _) => Err ( "not 2" ) ,
241
+ None => Err ( "not 2" ) ,
242
+ }
243
+ . into ( )
199
244
} ) ;
200
245
201
246
assert_eq ! (
@@ -208,14 +253,37 @@ mod tests {
208
253
) ;
209
254
}
210
255
256
+ #[ test]
257
+ fn fatal_errors ( ) {
258
+ let mut collection = vec ! [ 1 ] . into_iter ( ) ;
259
+
260
+ let res = retry ( NoDelay . take ( 2 ) , || match collection. next ( ) {
261
+ Some ( n) if n == 2 => OperationResult :: Ok ( n) ,
262
+ Some ( _) => OperationResult :: Err ( "no retry" ) ,
263
+ None => OperationResult :: Err ( "not 2" ) ,
264
+ } ) ;
265
+
266
+ assert_eq ! (
267
+ res,
268
+ Err ( Error :: Operation {
269
+ error: "no retry" ,
270
+ tries: 1 ,
271
+ total_delay: Duration :: from_millis( 0 )
272
+ } )
273
+ ) ;
274
+ }
275
+
211
276
#[ test]
212
277
fn succeeds_with_fixed_delay ( ) {
213
278
let mut collection = vec ! [ 1 , 2 ] . into_iter ( ) ;
214
279
215
- let value = retry ( Fixed :: from_millis ( 1 ) , || match collection. next ( ) {
216
- Some ( n) if n == 2 => Ok ( n) ,
217
- Some ( _) => Err ( "not 2" ) ,
218
- None => Err ( "not 2" ) ,
280
+ let value = retry ( Fixed :: from_millis ( 1 ) , || {
281
+ match collection. next ( ) {
282
+ Some ( n) if n == 2 => Ok ( n) ,
283
+ Some ( _) => Err ( "not 2" ) ,
284
+ None => Err ( "not 2" ) ,
285
+ }
286
+ . into ( )
219
287
} )
220
288
. unwrap ( ) ;
221
289
@@ -226,10 +294,13 @@ mod tests {
226
294
fn succeeds_with_exponential_delay ( ) {
227
295
let mut collection = vec ! [ 1 , 2 ] . into_iter ( ) ;
228
296
229
- let value = retry ( Exponential :: from_millis ( 1 ) , || match collection. next ( ) {
230
- Some ( n) if n == 2 => Ok ( n) ,
231
- Some ( _) => Err ( "not 2" ) ,
232
- None => Err ( "not 2" ) ,
297
+ let value = retry ( Exponential :: from_millis ( 1 ) , || {
298
+ match collection. next ( ) {
299
+ Some ( n) if n == 2 => Ok ( n) ,
300
+ Some ( _) => Err ( "not 2" ) ,
301
+ None => Err ( "not 2" ) ,
302
+ }
303
+ . into ( )
233
304
} )
234
305
. unwrap ( ) ;
235
306
@@ -240,10 +311,13 @@ mod tests {
240
311
fn succeeds_with_ranged_delay ( ) {
241
312
let mut collection = vec ! [ 1 , 2 ] . into_iter ( ) ;
242
313
243
- let value = retry ( Range :: from_millis_exclusive ( 1 , 10 ) , || match collection. next ( ) {
244
- Some ( n) if n == 2 => Ok ( n) ,
245
- Some ( _) => Err ( "not 2" ) ,
246
- None => Err ( "not 2" ) ,
314
+ let value = retry ( Range :: from_millis_exclusive ( 1 , 10 ) , || {
315
+ match collection. next ( ) {
316
+ Some ( n) if n == 2 => Ok ( n) ,
317
+ Some ( _) => Err ( "not 2" ) ,
318
+ None => Err ( "not 2" ) ,
319
+ }
320
+ . into ( )
247
321
} )
248
322
. unwrap ( ) ;
249
323
0 commit comments