@@ -185,6 +185,15 @@ pub enum ClientConfigKey {
185185 /// Supported keys:
186186 /// - `randomize_addresses`
187187 RandomizeAddresses ,
188+ /// Read timeout
189+ ///
190+ /// The timeout applies to each read operation, and resets after a
191+ /// successful read. This is useful for detecting stalled connections
192+ /// when the size of the response is not known beforehand.
193+ ///
194+ /// Supported keys:
195+ /// - `read_timeout`
196+ ReadTimeout ,
188197 /// Request timeout
189198 ///
190199 /// The timeout is applied from when the request starts connecting until the
@@ -219,6 +228,7 @@ impl AsRef<str> for ClientConfigKey {
219228 Self :: ProxyCaCertificate => "proxy_ca_certificate" ,
220229 Self :: ProxyExcludes => "proxy_excludes" ,
221230 Self :: RandomizeAddresses => "randomize_addresses" ,
231+ Self :: ReadTimeout => "read_timeout" ,
222232 Self :: Timeout => "timeout" ,
223233 Self :: UserAgent => "user_agent" ,
224234 }
@@ -246,6 +256,7 @@ impl FromStr for ClientConfigKey {
246256 "proxy_ca_certificate" => Ok ( Self :: ProxyCaCertificate ) ,
247257 "proxy_excludes" => Ok ( Self :: ProxyExcludes ) ,
248258 "randomize_addresses" => Ok ( Self :: RandomizeAddresses ) ,
259+ "read_timeout" => Ok ( Self :: ReadTimeout ) ,
249260 "timeout" => Ok ( Self :: Timeout ) ,
250261 "user_agent" => Ok ( Self :: UserAgent ) ,
251262 _ => Err ( super :: Error :: UnknownConfigurationKey {
@@ -323,6 +334,7 @@ pub struct ClientOptions {
323334 allow_insecure : ConfigValue < bool > ,
324335 timeout : Option < ConfigValue < Duration > > ,
325336 connect_timeout : Option < ConfigValue < Duration > > ,
337+ read_timeout : Option < ConfigValue < Duration > > ,
326338 pool_idle_timeout : Option < ConfigValue < Duration > > ,
327339 pool_max_idle_per_host : Option < ConfigValue < usize > > ,
328340 http2_keep_alive_interval : Option < ConfigValue < Duration > > ,
@@ -357,6 +369,7 @@ impl Default for ClientOptions {
357369 allow_insecure : Default :: default ( ) ,
358370 timeout : Some ( Duration :: from_secs ( 30 ) . into ( ) ) ,
359371 connect_timeout : Some ( Duration :: from_secs ( 5 ) . into ( ) ) ,
372+ read_timeout : None ,
360373 pool_idle_timeout : None ,
361374 pool_max_idle_per_host : None ,
362375 http2_keep_alive_interval : None ,
@@ -387,6 +400,9 @@ impl ClientOptions {
387400 ClientConfigKey :: ConnectTimeout => {
388401 self . connect_timeout = Some ( ConfigValue :: Deferred ( value. into ( ) ) )
389402 }
403+ ClientConfigKey :: ReadTimeout => {
404+ self . read_timeout = Some ( ConfigValue :: Deferred ( value. into ( ) ) )
405+ }
390406 ClientConfigKey :: DefaultContentType => self . default_content_type = Some ( value. into ( ) ) ,
391407 ClientConfigKey :: Http1Only => self . http1_only . parse ( value) ,
392408 ClientConfigKey :: Http2Only => self . http2_only . parse ( value) ,
@@ -428,6 +444,7 @@ impl ClientOptions {
428444 ClientConfigKey :: AllowHttp => Some ( self . allow_http . to_string ( ) ) ,
429445 ClientConfigKey :: AllowInvalidCertificates => Some ( self . allow_insecure . to_string ( ) ) ,
430446 ClientConfigKey :: ConnectTimeout => self . connect_timeout . as_ref ( ) . map ( fmt_duration) ,
447+ ClientConfigKey :: ReadTimeout => self . read_timeout . as_ref ( ) . map ( fmt_duration) ,
431448 ClientConfigKey :: DefaultContentType => self . default_content_type . clone ( ) ,
432449 ClientConfigKey :: Http1Only => Some ( self . http1_only . to_string ( ) ) ,
433450 ClientConfigKey :: Http2KeepAliveInterval => {
@@ -652,6 +669,36 @@ impl ClientOptions {
652669 self
653670 }
654671
672+ /// Set a read timeout
673+ ///
674+ /// The timeout applies to each read operation, and resets after a
675+ /// successful read. This is useful for detecting stalled connections
676+ /// when the size of the response is not known beforehand.
677+ ///
678+ /// Timeout errors are retried, subject to the [`RetryConfig`]
679+ ///
680+ /// Default is disabled (no read timeout)
681+ ///
682+ /// # See Also
683+ /// * [`Self::with_read_timeout_disabled`] to disable the read timeout
684+ /// * [`Self::with_timeout`] to set a timeout for the overall request
685+ /// * [`Self::with_connect_timeout`] to set a timeout for the connect phase
686+ ///
687+ /// [`RetryConfig`]: crate::RetryConfig
688+ pub fn with_read_timeout ( mut self , timeout : Duration ) -> Self {
689+ self . read_timeout = Some ( ConfigValue :: Parsed ( timeout) ) ;
690+ self
691+ }
692+
693+ /// Disables the read timeout
694+ ///
695+ /// # See Also
696+ /// * [`Self::with_read_timeout`]
697+ pub fn with_read_timeout_disabled ( mut self ) -> Self {
698+ self . read_timeout = None ;
699+ self
700+ }
701+
655702 /// Set the pool max idle timeout
656703 ///
657704 /// This is the length of time an idle connection will be kept alive
@@ -787,6 +834,10 @@ impl ClientOptions {
787834 builder = builder. connect_timeout ( timeout. get ( ) ?)
788835 }
789836
837+ if let Some ( timeout) = & self . read_timeout {
838+ builder = builder. read_timeout ( timeout. get ( ) ?)
839+ }
840+
790841 if let Some ( timeout) = & self . pool_idle_timeout {
791842 builder = builder. pool_idle_timeout ( timeout. get ( ) ?)
792843 }
@@ -1019,6 +1070,7 @@ mod tests {
10191070 let pool_idle_timeout = "93 seconds" . to_string ( ) ;
10201071 let pool_max_idle_per_host = "94" . to_string ( ) ;
10211072 let proxy_url = "https://fake_proxy_url" . to_string ( ) ;
1073+ let read_timeout = "45 seconds" . to_string ( ) ;
10221074 let timeout = "95 seconds" . to_string ( ) ;
10231075 let user_agent = "object_store:fake_user_agent" . to_string ( ) ;
10241076
@@ -1045,6 +1097,7 @@ mod tests {
10451097 ( "pool_idle_timeout" , pool_idle_timeout. clone ( ) ) ,
10461098 ( "pool_max_idle_per_host" , pool_max_idle_per_host. clone ( ) ) ,
10471099 ( "proxy_url" , proxy_url. clone ( ) ) ,
1100+ ( "read_timeout" , read_timeout. clone ( ) ) ,
10481101 ( "timeout" , timeout. clone ( ) ) ,
10491102 ( "user_agent" , user_agent. clone ( ) ) ,
10501103 ] ) ;
@@ -1134,6 +1187,12 @@ mod tests {
11341187 . unwrap( ) ,
11351188 proxy_url
11361189 ) ;
1190+ assert_eq ! (
1191+ builder
1192+ . get_config_value( & ClientConfigKey :: ReadTimeout )
1193+ . unwrap( ) ,
1194+ read_timeout
1195+ ) ;
11371196 assert_eq ! (
11381197 builder. get_config_value( & ClientConfigKey :: Timeout ) . unwrap( ) ,
11391198 timeout
0 commit comments