Skip to content

Commit 00468c5

Browse files
apollo_infra: add keepalive time greater than 1 validation
1 parent 7445717 commit 00468c5

2 files changed

Lines changed: 47 additions & 20 deletions

File tree

crates/apollo_infra/src/component_client/remote_component_client.rs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub struct RemoteClientConfig {
6363
pub idle_connections: usize,
6464
// Determines client connection timeouts. Used plainly for HTTP/2 connections, and with a
6565
// `TCP_KEEPALIVE_FACTOR` for TCP connections.
66-
#[validate(custom(function = "validate_tcp_exceeds_http_keepalive"))]
66+
#[validate(custom(function = "validate_keepalive_timeout_ms"))]
6767
pub keepalive_timeout_ms: u64,
6868
pub attempts_per_log: usize,
6969
pub initial_retry_delay_ms: u64,
@@ -91,30 +91,51 @@ impl Default for RemoteClientConfig {
9191
}
9292
}
9393

94-
/// Validates that the TCP keepalive duration (at second granularity, as the OS stores
95-
/// `TCP_KEEPIDLE` in whole seconds) is greater than or equal to the HTTP keepalive duration
96-
/// (millisecond granularity). If the configured `keepalive_timeout_ms * TCP_KEEPALIVE_FACTOR` is
97-
/// less than 1 second, truncation to whole seconds yields 0 s, making the TCP keepalive shorter
98-
/// than the HTTP keepalive.
99-
fn validate_tcp_exceeds_http_keepalive(keepalive_timeout_ms: u64) -> Result<(), ValidationError> {
94+
/// Validates that `keepalive_timeout_ms` is positive, and that the derived TCP keepalive duration
95+
/// (at second granularity, as the OS stores `TCP_KEEPIDLE` in whole seconds) is greater than or
96+
/// equal to the HTTP keepalive 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> {
103+
if keepalive_timeout_ms == 0 {
104+
return Err(create_validation_error(
105+
"keepalive_timeout_ms must be greater than 0".to_string(),
106+
"keepalive_timeout_ms_zero",
107+
"keepalive_timeout_ms must be > 0.",
108+
));
109+
}
100110
let http_keepalive = Duration::from_millis(keepalive_timeout_ms);
101111
let tcp_keepalive_raw = http_keepalive.mul_f64(TCP_KEEPALIVE_FACTOR);
102112
// TCP_KEEPIDLE is stored in whole seconds; fractional seconds are truncated by the OS.
103-
let tcp_keepalive = Duration::from_secs(tcp_keepalive_raw.as_secs());
104-
if tcp_keepalive >= http_keepalive {
105-
Ok(())
106-
} else {
107-
Err(create_validation_error(
113+
let tcp_keepalive_secs = tcp_keepalive_raw.as_secs();
114+
if tcp_keepalive_secs == 0 {
115+
return Err(create_validation_error(
116+
format!(
117+
"TCP keepalive rounds to 0 s (keepalive_timeout_ms={keepalive_timeout_ms}, \
118+
factor={TCP_KEEPALIVE_FACTOR}): increase keepalive_timeout_ms so that \
119+
keepalive_timeout_ms * {TCP_KEEPALIVE_FACTOR} is at least 1 s",
120+
),
121+
"tcp_keepalive_zero",
122+
"TCP keepalive (second granularity) must be > 0 s.",
123+
));
124+
}
125+
let tcp_keepalive = Duration::from_secs(tcp_keepalive_secs);
126+
if tcp_keepalive < http_keepalive {
127+
return Err(create_validation_error(
108128
format!(
109-
"TCP keepalive ({} s) is shorter than HTTP keepalive ({keepalive_timeout_ms} ms): \
110-
increase keepalive_timeout_ms so that keepalive_timeout_ms * \
111-
{TCP_KEEPALIVE_FACTOR} rounds to at least {keepalive_timeout_ms} ms",
112-
tcp_keepalive.as_secs(),
129+
"TCP keepalive ({tcp_keepalive_secs} s) is shorter than HTTP keepalive \
130+
({keepalive_timeout_ms} ms): increase keepalive_timeout_ms so that \
131+
keepalive_timeout_ms * {TCP_KEEPALIVE_FACTOR} rounds to at least \
132+
{keepalive_timeout_ms} ms",
113133
),
114134
"tcp_keepalive_shorter_than_http_keepalive",
115135
"TCP keepalive (second granularity) must be >= HTTP keepalive (ms granularity).",
116-
))
136+
));
117137
}
138+
Ok(())
118139
}
119140

120141
impl SerializeConfig for RemoteClientConfig {

crates/apollo_infra/src/component_client/remote_component_client_test.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ fn tcp_keepalive_validation_passes_when_tcp_equals_http_keepalive() {
1818
assert!(config.validate().is_ok());
1919
}
2020

21-
// keepalive_timeout_ms = 100 ms: tcp_raw = 150 ms, tcp_whole_secs = 0 s = 0 ms, which is less than
22-
// http_keepalive = 100 ms.
21+
// keepalive_timeout_ms = 1100 ms: tcp_raw = 1650 ms, tcp_whole_secs = 1 s = 1000 ms, which is
22+
// less than http_keepalive = 1100 ms.
2323
#[test]
2424
fn tcp_keepalive_validation_fails_when_tcp_truncated_below_http_keepalive() {
25-
let config = RemoteClientConfig { keepalive_timeout_ms: 100, ..Default::default() };
25+
let config = RemoteClientConfig { keepalive_timeout_ms: 1100, ..Default::default() };
26+
assert!(config.validate().is_err());
27+
}
28+
29+
#[test]
30+
fn tcp_keepalive_validation_fails_when_keepalive_timeout_ms_is_zero() {
31+
let config = RemoteClientConfig { keepalive_timeout_ms: 0, ..Default::default() };
2632
assert!(config.validate().is_err());
2733
}

0 commit comments

Comments
 (0)