@@ -18,7 +18,6 @@ use hyper_util::client::legacy::Client;
1818use hyper_util:: rt:: TokioExecutor ;
1919use serde:: de:: DeserializeOwned ;
2020use serde:: { Deserialize , Serialize } ;
21- use static_assertions:: const_assert;
2221use tokio:: sync:: Mutex ;
2322use tokio:: time:: Instant ;
2423use tracing:: field:: { display, Empty } ;
@@ -32,6 +31,7 @@ use crate::component_definitions::{
3231 ServerError ,
3332 APPLICATION_OCTET_STREAM ,
3433 REQUEST_ID_HEADER ,
34+ TCP_KEEPALIVE_FACTOR ,
3535} ;
3636use crate :: metrics:: RemoteClientMetrics ;
3737use crate :: requests:: LabeledRequest ;
@@ -45,10 +45,6 @@ pub const DEFAULT_RETRIES: usize = 15;
4545pub const REQUEST_TIMEOUT_ERROR_MESSAGE : & str = "request timed out" ;
4646
4747const DEFAULT_IDLE_CONNECTIONS : usize = 10 ;
48- pub ( crate ) const TCP_IDLE_TIMEOUT_FACTOR : f64 = 1.5 ;
49- // Ensure tcp connection timeout is greater than http2 connection timeout by requiring a factor
50- // greater than 1.
51- const_assert ! ( TCP_IDLE_TIMEOUT_FACTOR > 1.0 ) ;
5248
5349// 8 MiB — bounds memory materialized from a single response as defense in depth.
5450const DEFAULT_MAX_RESPONSE_BODY_BYTES : usize = 8 * 1024 * 1024 ;
@@ -66,8 +62,8 @@ pub struct RemoteClientConfig {
6662 pub retries : usize ,
6763 pub idle_connections : usize ,
6864 // Determines client connection timeouts. Used plainly for HTTP/2 connections, and with a
69- // `TCP_IDLE_TIMEOUT_FACTOR ` for TCP connections.
70- #[ validate( custom( function = "validate_tcp_exceeds_http_keepalive " ) ) ]
65+ // `TCP_KEEPALIVE_FACTOR ` for TCP connections.
66+ #[ validate( custom( function = "validate_keepalive_timeout_ms " ) ) ]
7167 pub keepalive_timeout_ms : u64 ,
7268 pub attempts_per_log : usize ,
7369 pub initial_retry_delay_ms : u64 ,
@@ -96,24 +92,39 @@ impl Default for RemoteClientConfig {
9692}
9793
9894/// Validates that the TCP keepalive duration (at second granularity, as the OS stores
99- /// `TCP_KEEPIDLE` in whole seconds) is greater than or equal to the HTTP keepalive duration
100- /// (millisecond granularity). If the configured `keepalive_timeout_ms * TCP_IDLE_TIMEOUT_FACTOR` is
101- /// less than 1 second, truncation to whole seconds yields 0 s, making the TCP keepalive shorter
102- /// than the HTTP keepalive.
103- fn validate_tcp_exceeds_http_keepalive ( keepalive_timeout_ms : u64 ) -> Result < ( ) , ValidationError > {
95+ /// `TCP_KEEPIDLE` in whole seconds) is positive and greater than or equal to the HTTP keepalive
96+ /// duration (millisecond granularity). If the configured
97+ /// `keepalive_timeout_ms * TCP_KEEPALIVE_FACTOR` rounds to 0 s, the kernel rejects the socket
98+ /// option with `EINVAL`. If it is less than `keepalive_timeout_ms`, the TCP keepalive fires before
99+ /// the HTTP-level timeout.
100+ pub ( crate ) fn validate_keepalive_timeout_ms (
101+ keepalive_timeout_ms : u64 ,
102+ ) -> Result < ( ) , ValidationError > {
104103 let http_keepalive = Duration :: from_millis ( keepalive_timeout_ms) ;
105- let tcp_keepalive_raw = http_keepalive. mul_f64 ( TCP_IDLE_TIMEOUT_FACTOR ) ;
104+ let tcp_keepalive_raw = http_keepalive. mul_f64 ( TCP_KEEPALIVE_FACTOR ) ;
106105 // TCP_KEEPIDLE is stored in whole seconds; fractional seconds are truncated by the OS.
107- let tcp_keepalive = Duration :: from_secs ( tcp_keepalive_raw. as_secs ( ) ) ;
106+ let tcp_keepalive_secs = tcp_keepalive_raw. as_secs ( ) ;
107+ if tcp_keepalive_secs == 0 {
108+ return Err ( create_validation_error (
109+ format ! (
110+ "TCP keepalive rounds to 0 s (keepalive_timeout_ms={keepalive_timeout_ms}, \
111+ factor={TCP_KEEPALIVE_FACTOR}): increase keepalive_timeout_ms so that \
112+ keepalive_timeout_ms * {TCP_KEEPALIVE_FACTOR} is at least 1 s",
113+ ) ,
114+ "tcp_keepalive_zero" ,
115+ "TCP keepalive (second granularity) must be > 0 s." ,
116+ ) ) ;
117+ }
118+ let tcp_keepalive = Duration :: from_secs ( tcp_keepalive_secs) ;
108119 if tcp_keepalive >= http_keepalive {
109120 Ok ( ( ) )
110121 } else {
111122 Err ( create_validation_error (
112123 format ! (
113- "TCP keepalive ({} s) is shorter than HTTP keepalive ({keepalive_timeout_ms} ms): \
114- increase keepalive_timeout_ms so that keepalive_timeout_ms * \
115- {TCP_IDLE_TIMEOUT_FACTOR } rounds to at least {keepalive_timeout_ms} ms" ,
116- tcp_keepalive . as_secs ( ) ,
124+ "TCP keepalive ({tcp_keepalive_secs } s) is shorter than HTTP keepalive \
125+ ({keepalive_timeout_ms} ms): increase keepalive_timeout_ms so that \
126+ keepalive_timeout_ms * {TCP_KEEPALIVE_FACTOR } rounds to at least \
127+ {keepalive_timeout_ms} ms" ,
117128 ) ,
118129 "tcp_keepalive_shorter_than_http_keepalive" ,
119130 "TCP keepalive (second granularity) must be >= HTTP keepalive (ms granularity)." ,
@@ -230,7 +241,7 @@ where
230241 let mut connector = HttpConnector :: new ( ) ;
231242 connector. set_nodelay ( config. set_tcp_nodelay ) ;
232243 connector. set_connect_timeout ( Some ( Duration :: from_millis ( config. connection_timeout_ms ) ) ) ;
233- connector. set_keepalive ( Some ( idle_timeout. mul_f64 ( TCP_IDLE_TIMEOUT_FACTOR ) ) ) ;
244+ connector. set_keepalive ( Some ( idle_timeout. mul_f64 ( TCP_KEEPALIVE_FACTOR ) ) ) ;
234245
235246 // Create the HTTP/2 client.
236247 let client = Client :: builder ( TokioExecutor :: new ( ) )
0 commit comments