3
3
//! The `RetryPolicy` struct defines the configuration for the retry behavior, including the maximum
4
4
//! number of retries, initial delay, maximum delay, and jitter.
5
5
//!
6
- //! The `retry_with_exponential_backoff ` function retries the given operation according to the
6
+ //! The `retry_with_backoff ` function retries the given operation according to the
7
7
//! specified retry policy, using exponential backoff and jitter to determine the delay between
8
- //! retries. The function logs errors and retries the operation until it succeeds or the maximum
9
- //! number of retries is reached .
8
+ //! retries. The function uses error classification to determine retry behavior and can honor
9
+ //! server-provided throttling hints .
10
10
11
11
#[ cfg( feature = "experimental_async_runtime" ) ]
12
12
use opentelemetry:: otel_warn;
@@ -69,45 +69,7 @@ fn generate_jitter(max_jitter: u64) -> u64 {
69
69
nanos as u64 % ( max_jitter + 1 )
70
70
}
71
71
72
- /// Retries the given operation with exponential backoff and jitter.
73
- ///
74
- /// # Arguments
75
- ///
76
- /// * `runtime` - The async runtime to use for delays.
77
- /// * `policy` - The retry policy configuration.
78
- /// * `operation_name` - The name of the operation being retried.
79
- /// * `operation` - The operation to be retried.
80
- ///
81
- /// # Returns
82
- ///
83
- /// A `Result` containing the operation's result or an error if the maximum retries are reached.
84
- #[ cfg( feature = "experimental_async_runtime" ) ]
85
- pub async fn retry_with_exponential_backoff < R , F , Fut , T , E > (
86
- runtime : R ,
87
- policy : RetryPolicy ,
88
- operation_name : & str ,
89
- operation : F ,
90
- ) -> Result < T , E >
91
- where
92
- R : Runtime ,
93
- F : FnMut ( ) -> Fut ,
94
- E : std:: fmt:: Debug ,
95
- Fut : Future < Output = Result < T , E > > ,
96
- {
97
- // Use a simple classifier that treats all errors as retryable
98
- let simple_classifier = |_: & E | RetryErrorType :: Retryable ;
99
-
100
- retry_with_exponential_backoff_classified (
101
- runtime,
102
- policy,
103
- simple_classifier,
104
- operation_name,
105
- operation,
106
- )
107
- . await
108
- }
109
-
110
- /// Enhanced retry with exponential backoff, jitter, and error classification.
72
+ /// Retries the given operation with exponential backoff, jitter, and error classification.
111
73
///
112
74
/// This function provides sophisticated retry behavior by classifying errors
113
75
/// and honoring server-provided throttling hints (e.g., HTTP Retry-After, gRPC RetryInfo).
125
87
/// A `Result` containing the operation's result or an error if max retries are reached
126
88
/// or a non-retryable error occurs.
127
89
#[ cfg( feature = "experimental_async_runtime" ) ]
128
- pub async fn retry_with_exponential_backoff_classified < R , F , Fut , T , E , C > (
90
+ pub async fn retry_with_backoff < R , F , Fut , T , E , C > (
129
91
runtime : R ,
130
92
policy : RetryPolicy ,
131
93
error_classifier : C ,
@@ -186,9 +148,10 @@ where
186
148
/// No-op retry function for when experimental_async_runtime is not enabled.
187
149
/// This function will execute the operation exactly once without any retries.
188
150
#[ cfg( not( feature = "experimental_async_runtime" ) ) ]
189
- pub async fn retry_with_exponential_backoff < R , F , Fut , T , E > (
151
+ pub async fn retry_with_backoff < R , F , Fut , T , E , C > (
190
152
_runtime : R ,
191
153
_policy : RetryPolicy ,
154
+ _error_classifier : C ,
192
155
_operation_name : & str ,
193
156
mut operation : F ,
194
157
) -> Result < T , E >
@@ -227,9 +190,13 @@ mod tests {
227
190
jitter_ms : 100 ,
228
191
} ;
229
192
230
- let result = retry_with_exponential_backoff ( runtime, policy, "test_operation" , || {
231
- Box :: pin ( async { Ok :: < _ , ( ) > ( "success" ) } )
232
- } )
193
+ let result = retry_with_backoff (
194
+ runtime,
195
+ policy,
196
+ |_: & ( ) | RetryErrorType :: Retryable ,
197
+ "test_operation" ,
198
+ || Box :: pin ( async { Ok :: < _ , ( ) > ( "success" ) } ) ,
199
+ )
233
200
. await ;
234
201
235
202
assert_eq ! ( result, Ok ( "success" ) ) ;
@@ -248,16 +215,22 @@ mod tests {
248
215
249
216
let attempts = AtomicUsize :: new ( 0 ) ;
250
217
251
- let result = retry_with_exponential_backoff ( runtime, policy, "test_operation" , || {
252
- let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
253
- Box :: pin ( async move {
254
- if attempt < 2 {
255
- Err :: < & str , & str > ( "error" ) // Fail the first two attempts
256
- } else {
257
- Ok :: < & str , & str > ( "success" ) // Succeed on the third attempt
258
- }
259
- } )
260
- } )
218
+ let result = retry_with_backoff (
219
+ runtime,
220
+ policy,
221
+ |_: & & str | RetryErrorType :: Retryable ,
222
+ "test_operation" ,
223
+ || {
224
+ let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
225
+ Box :: pin ( async move {
226
+ if attempt < 2 {
227
+ Err :: < & str , & str > ( "error" ) // Fail the first two attempts
228
+ } else {
229
+ Ok :: < & str , & str > ( "success" ) // Succeed on the third attempt
230
+ }
231
+ } )
232
+ } ,
233
+ )
261
234
. await ;
262
235
263
236
assert_eq ! ( result, Ok ( "success" ) ) ;
@@ -277,10 +250,16 @@ mod tests {
277
250
278
251
let attempts = AtomicUsize :: new ( 0 ) ;
279
252
280
- let result = retry_with_exponential_backoff ( runtime, policy, "test_operation" , || {
281
- attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
282
- Box :: pin ( async { Err :: < ( ) , _ > ( "error" ) } ) // Always fail
283
- } )
253
+ let result = retry_with_backoff (
254
+ runtime,
255
+ policy,
256
+ |_: & & str | RetryErrorType :: Retryable ,
257
+ "test_operation" ,
258
+ || {
259
+ attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
260
+ Box :: pin ( async { Err :: < ( ) , _ > ( "error" ) } ) // Always fail
261
+ } ,
262
+ )
284
263
. await ;
285
264
286
265
assert_eq ! ( result, Err ( "error" ) ) ;
@@ -300,9 +279,15 @@ mod tests {
300
279
301
280
let result = timeout (
302
281
Duration :: from_secs ( 1 ) ,
303
- retry_with_exponential_backoff ( runtime, policy, "test_operation" , || {
304
- Box :: pin ( async { Err :: < ( ) , _ > ( "error" ) } ) // Always fail
305
- } ) ,
282
+ retry_with_backoff (
283
+ runtime,
284
+ policy,
285
+ |_: & & str | RetryErrorType :: Retryable ,
286
+ "test_operation" ,
287
+ || {
288
+ Box :: pin ( async { Err :: < ( ) , _ > ( "error" ) } ) // Always fail
289
+ } ,
290
+ ) ,
306
291
)
307
292
. await ;
308
293
@@ -337,16 +322,10 @@ mod tests {
337
322
// Classifier that returns non-retryable
338
323
let classifier = |_: & ( ) | RetryErrorType :: NonRetryable ;
339
324
340
- let result = retry_with_exponential_backoff_classified (
341
- runtime,
342
- policy,
343
- classifier,
344
- "test_operation" ,
345
- || {
346
- attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
347
- Box :: pin ( async { Err :: < ( ) , _ > ( ( ) ) } ) // Always fail
348
- } ,
349
- )
325
+ let result = retry_with_backoff ( runtime, policy, classifier, "test_operation" , || {
326
+ attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
327
+ Box :: pin ( async { Err :: < ( ) , _ > ( ( ) ) } ) // Always fail
328
+ } )
350
329
. await ;
351
330
352
331
assert ! ( result. is_err( ) ) ;
@@ -368,22 +347,16 @@ mod tests {
368
347
// Classifier that returns retryable
369
348
let classifier = |_: & ( ) | RetryErrorType :: Retryable ;
370
349
371
- let result = retry_with_exponential_backoff_classified (
372
- runtime,
373
- policy,
374
- classifier,
375
- "test_operation" ,
376
- || {
377
- let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
378
- Box :: pin ( async move {
379
- if attempt < 1 {
380
- Err :: < & str , ( ) > ( ( ) ) // Fail first attempt
381
- } else {
382
- Ok ( "success" ) // Succeed on retry
383
- }
384
- } )
385
- } ,
386
- )
350
+ let result = retry_with_backoff ( runtime, policy, classifier, "test_operation" , || {
351
+ let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
352
+ Box :: pin ( async move {
353
+ if attempt < 1 {
354
+ Err :: < & str , ( ) > ( ( ) ) // Fail first attempt
355
+ } else {
356
+ Ok ( "success" ) // Succeed on retry
357
+ }
358
+ } )
359
+ } )
387
360
. await ;
388
361
389
362
assert_eq ! ( result, Ok ( "success" ) ) ;
@@ -407,22 +380,16 @@ mod tests {
407
380
408
381
let start_time = std:: time:: Instant :: now ( ) ;
409
382
410
- let result = retry_with_exponential_backoff_classified (
411
- runtime,
412
- policy,
413
- classifier,
414
- "test_operation" ,
415
- || {
416
- let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
417
- Box :: pin ( async move {
418
- if attempt < 1 {
419
- Err :: < & str , ( ) > ( ( ) ) // Fail first attempt (will be throttled)
420
- } else {
421
- Ok ( "success" ) // Succeed on retry
422
- }
423
- } )
424
- } ,
425
- )
383
+ let result = retry_with_backoff ( runtime, policy, classifier, "test_operation" , || {
384
+ let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
385
+ Box :: pin ( async move {
386
+ if attempt < 1 {
387
+ Err :: < & str , ( ) > ( ( ) ) // Fail first attempt (will be throttled)
388
+ } else {
389
+ Ok ( "success" ) // Succeed on retry
390
+ }
391
+ } )
392
+ } )
426
393
. await ;
427
394
428
395
let elapsed = start_time. elapsed ( ) ;
@@ -447,16 +414,10 @@ mod tests {
447
414
// Classifier that returns retryable
448
415
let classifier = |_: & ( ) | RetryErrorType :: Retryable ;
449
416
450
- let result = retry_with_exponential_backoff_classified (
451
- runtime,
452
- policy,
453
- classifier,
454
- "test_operation" ,
455
- || {
456
- attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
457
- Box :: pin ( async { Err :: < ( ) , _ > ( ( ) ) } ) // Always fail
458
- } ,
459
- )
417
+ let result = retry_with_backoff ( runtime, policy, classifier, "test_operation" , || {
418
+ attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
419
+ Box :: pin ( async { Err :: < ( ) , _ > ( ( ) ) } ) // Always fail
420
+ } )
460
421
. await ;
461
422
462
423
assert ! ( result. is_err( ) ) ;
@@ -482,22 +443,16 @@ mod tests {
482
443
_ => RetryErrorType :: Retryable ,
483
444
} ;
484
445
485
- let result = retry_with_exponential_backoff_classified (
486
- runtime,
487
- policy,
488
- classifier,
489
- "test_operation" ,
490
- || {
491
- let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
492
- Box :: pin ( async move {
493
- if attempt < 2 {
494
- Err ( attempt) // Return attempt number as error
495
- } else {
496
- Ok ( "success" ) // Succeed on third attempt
497
- }
498
- } )
499
- } ,
500
- )
446
+ let result = retry_with_backoff ( runtime, policy, classifier, "test_operation" , || {
447
+ let attempt = attempts. fetch_add ( 1 , Ordering :: SeqCst ) ;
448
+ Box :: pin ( async move {
449
+ if attempt < 2 {
450
+ Err ( attempt) // Return attempt number as error
451
+ } else {
452
+ Ok ( "success" ) // Succeed on third attempt
453
+ }
454
+ } )
455
+ } )
501
456
. await ;
502
457
503
458
assert_eq ! ( result, Ok ( "success" ) ) ;
0 commit comments