@@ -149,18 +149,24 @@ where
149
149
// only start keeping track of time after the first request is made
150
150
let start = start. get_or_insert_with ( OffsetDateTime :: now_utc) ;
151
151
let ( last_error, retry_after) = match result {
152
- Ok ( response) if response. status ( ) . is_success ( ) => {
153
- trace ! (
154
- ?request,
155
- ?response,
156
- "server returned success status {}" ,
157
- response. status( ) ,
158
- ) ;
159
- return Ok ( response) ;
160
- }
161
152
Ok ( response) => {
162
- // Error status code
163
153
let status = response. status ( ) ;
154
+ if !RETRY_STATUSES . contains ( & status) {
155
+ if status. is_success ( ) {
156
+ trace ! (
157
+ ?request,
158
+ ?response,
159
+ "server returned success status {}" ,
160
+ status,
161
+ ) ;
162
+ } else {
163
+ debug ! (
164
+ "server returned status which will not be retried: {}" ,
165
+ status
166
+ ) ;
167
+ }
168
+ return Ok ( response) ;
169
+ }
164
170
165
171
// For a 429 response (TooManyRequests) or 503 (ServiceUnavailable),
166
172
// use any "retry-after" headers returned by the server to determine how long to wait before retrying.
@@ -179,21 +185,6 @@ where
179
185
http_error. error_code ( ) . map ( std:: borrow:: ToOwned :: to_owned) ,
180
186
) ;
181
187
182
- if !RETRY_STATUSES . contains ( & status) {
183
- debug ! (
184
- "server returned error status which will not be retried: {}" ,
185
- status
186
- ) ;
187
- // Server didn't return a status we retry on so return early
188
- let error = Error :: full (
189
- error_kind,
190
- http_error,
191
- format ! (
192
- "server returned error status which will not be retried: {status}"
193
- ) ,
194
- ) ;
195
- return Err ( error) ;
196
- }
197
188
debug ! (
198
189
"server returned error status which requires retry: {}" ,
199
190
status
@@ -233,7 +224,28 @@ where
233
224
#[ cfg( test) ]
234
225
mod test {
235
226
use super :: * ;
227
+ use crate :: http:: {
228
+ headers:: Headers , Context , FixedRetryOptions , Method , RawResponse , Request , RetryOptions ,
229
+ Url ,
230
+ } ;
236
231
use :: time:: macros:: datetime;
232
+ use std:: sync:: { Arc , Mutex } ;
233
+
234
+ // Policy that counts the requests it receives and returns responses having a given status code
235
+ #[ derive( Debug ) ]
236
+ struct StatusResponder {
237
+ request_count : Arc < Mutex < u32 > > ,
238
+ status : StatusCode ,
239
+ }
240
+
241
+ #[ async_trait]
242
+ impl Policy for StatusResponder {
243
+ async fn send ( & self , _: & Context , _: & mut Request , _: & [ Arc < dyn Policy > ] ) -> PolicyResult {
244
+ let mut count = self . request_count . lock ( ) . unwrap ( ) ;
245
+ * count += 1 ;
246
+ Ok ( RawResponse :: from_bytes ( self . status , Headers :: new ( ) , "" ) )
247
+ }
248
+ }
237
249
238
250
// A function that returns a fixed "now" value for testing.
239
251
fn datetime_now ( ) -> OffsetDateTime {
@@ -290,4 +302,57 @@ mod test {
290
302
let retry_after = get_retry_after ( & headers, datetime_now) ;
291
303
assert_eq ! ( retry_after, Some ( Duration :: seconds( 123 ) ) ) ;
292
304
}
305
+
306
+ #[ tokio:: test]
307
+ async fn test_retry_statuses ( ) {
308
+ let retries = 2u32 ;
309
+ let retry_policy = RetryOptions :: fixed (
310
+ FixedRetryOptions :: default ( )
311
+ . delay ( Duration :: nanoseconds ( 1 ) )
312
+ . max_retries ( retries) ,
313
+ )
314
+ . to_policy ( ) ;
315
+ let ctx = Context :: new ( ) ;
316
+ let url = Url :: parse ( "http://localhost" ) . unwrap ( ) ;
317
+
318
+ for & status in RETRY_STATUSES {
319
+ let mut request = Request :: new ( url. clone ( ) , Method :: Get ) ;
320
+ let count = Arc :: new ( Mutex :: new ( 0 ) ) ;
321
+ let mock = StatusResponder {
322
+ request_count : count. clone ( ) ,
323
+ status,
324
+ } ;
325
+ let next = vec ! [ Arc :: new( mock) as Arc <dyn Policy >] ;
326
+
327
+ retry_policy
328
+ . send ( & ctx, & mut request, & next)
329
+ . await
330
+ . expect_err ( "Policy should return an error after exhausting retries" ) ;
331
+
332
+ assert_eq ! (
333
+ retries + 1 ,
334
+ * count. lock( ) . unwrap( ) ,
335
+ "Policy should retry {status}"
336
+ ) ;
337
+ }
338
+
339
+ let mut request = Request :: new ( url. clone ( ) , Method :: Get ) ;
340
+ let count = Arc :: new ( Mutex :: new ( 0 ) ) ;
341
+ let next = vec ! [ Arc :: new( StatusResponder {
342
+ request_count: count. clone( ) ,
343
+ status: StatusCode :: Unauthorized ,
344
+ } ) as Arc <dyn Policy >] ;
345
+
346
+ let response = retry_policy
347
+ . send ( & ctx, & mut request, & next)
348
+ . await
349
+ . expect ( "Policy should return a response whose status isn't in RETRY_STATUSES" ) ;
350
+
351
+ assert_eq ! ( response. status( ) , StatusCode :: Unauthorized ) ;
352
+ assert_eq ! (
353
+ 1 ,
354
+ * count. lock( ) . unwrap( ) ,
355
+ "Policy shouldn't retry after receiving a response whose status isn't in RETRY_STATUSES"
356
+ ) ;
357
+ }
293
358
}
0 commit comments