@@ -4,7 +4,7 @@ mod data;
44use backon:: { ExponentialBuilder , Retryable } ;
55pub use data:: * ;
66
7- use crate :: http:: get_retry_after_from_response_header ;
7+ use crate :: http:: calculate_retry_after_from_response_header ;
88use reqwest:: { Client , ClientBuilder , IntoUrl , Method , Response } ;
99use std:: fmt:: Debug ;
1010use std:: future:: Future ;
@@ -37,9 +37,10 @@ pub enum Error {
3737#[ non_exhaustive]
3838#[ derive( Clone , Debug ) ]
3939pub struct FetcherOptions {
40- pub timeout : Duration ,
41- pub retries : usize ,
42- pub default_retry_after : Duration ,
40+ timeout : Duration ,
41+ retries : usize ,
42+ default_retry_after : Duration ,
43+ max_retry_after : Duration ,
4344}
4445
4546impl FetcherOptions {
@@ -61,8 +62,22 @@ impl FetcherOptions {
6162 }
6263
6364 /// Set the default retry-after duration when a 429 response doesn't include a Retry-After header.
64- pub fn default_retry_after ( mut self , duration : impl Into < Duration > ) -> Self {
65- self . default_retry_after = duration. into ( ) ;
65+ pub fn retry_after ( mut self , duration : Duration ) -> Self {
66+ if duration > self . max_retry_after {
67+ panic ! ( "Default retry-after cannot be greater than max retry-after (300s)" ) ;
68+ }
69+ self . default_retry_after = duration;
70+ self
71+ }
72+
73+ /// Set the default retry-after duration when a 429 response doesn't include a Retry-After header
74+ /// and checks the duration against the maximum retry-after.
75+ pub fn retry_after_with_max ( mut self , default : Duration , max : Duration ) -> Self {
76+ if default > max {
77+ panic ! ( "Default retry-after cannot be greater than max retry-after" ) ;
78+ }
79+ self . default_retry_after = default;
80+ self . max_retry_after = max;
6681 self
6782 }
6883}
@@ -73,6 +88,7 @@ impl Default for FetcherOptions {
7388 timeout : Duration :: from_secs ( 30 ) ,
7489 retries : 5 ,
7590 default_retry_after : Duration :: from_secs ( 10 ) ,
91+ max_retry_after : Duration :: from_mins ( 5 ) ,
7692 }
7793 }
7894}
@@ -124,33 +140,23 @@ impl Fetcher {
124140 let url = url. into_url ( ) ?;
125141
126142 let retries = self . retries ;
127- let backoff = ExponentialBuilder :: default ( ) ;
128-
129- ( || async {
130- match self . fetch_once ( url. clone ( ) , & processor) . await {
131- Ok ( result) => Ok ( result) ,
132- Err ( err) => {
133- log:: info!( "Failed to retrieve: {err}" ) ;
134- Err ( err)
135- }
136- }
137- } )
138- . retry ( & backoff. with_max_times ( retries) )
139- . notify ( |err, dur| {
140- // If rate limited, ensure we wait at least the Retry-After duration
141- if let Error :: RateLimited ( retry_after) = err {
142- if dur < * retry_after {
143- log:: info!(
144- "Rate limited, extending wait from {:?} to {:?}" ,
145- dur,
146- retry_after
147- ) ;
148- let additional = * retry_after - dur;
149- std:: thread:: sleep ( additional) ;
143+ let retry = ExponentialBuilder :: default ( ) . with_max_times ( retries) ;
144+
145+ ( || async { self . fetch_once ( url. clone ( ) , & processor) . await } )
146+ . retry ( retry)
147+ . adjust ( |e, dur| {
148+ if let Error :: RateLimited ( retry_after) = e {
149+ if let Some ( dur_value) = dur
150+ && dur_value > * retry_after
151+ {
152+ return dur;
153+ }
154+ Some ( * retry_after) // only use server-provided delay if it's longer
155+ } else {
156+ dur // minimum delay as per backoff strategy
150157 }
151- }
152- } )
153- . await
158+ } )
159+ . await
154160 }
155161
156162 async fn fetch_once < D : DataProcessor > (
@@ -164,7 +170,7 @@ impl Fetcher {
164170
165171 // Check for rate limiting
166172 if let Some ( retry_after) =
167- get_retry_after_from_response_header ( & response, self . default_retry_after )
173+ calculate_retry_after_from_response_header ( & response, self . default_retry_after )
168174 {
169175 log:: info!( "Rate limited (429), retry after: {:?}" , retry_after) ;
170176 return Err ( Error :: RateLimited ( retry_after) ) ;
0 commit comments