diff --git a/tonic/src/transport/channel/endpoint.rs b/tonic/src/transport/channel/endpoint.rs index 99ff11e9f..f5c386899 100644 --- a/tonic/src/transport/channel/endpoint.rs +++ b/tonic/src/transport/channel/endpoint.rs @@ -39,6 +39,8 @@ pub struct Endpoint { pub(crate) init_stream_window_size: Option, pub(crate) init_connection_window_size: Option, pub(crate) tcp_keepalive: Option, + pub(crate) tcp_keepalive_interval: Option, + pub(crate) tcp_keepalive_retries: Option, pub(crate) tcp_nodelay: bool, pub(crate) http2_keep_alive_interval: Option, pub(crate) http2_keep_alive_timeout: Option, @@ -84,6 +86,8 @@ impl Endpoint { init_stream_window_size: None, init_connection_window_size: None, tcp_keepalive: None, + tcp_keepalive_interval: None, + tcp_keepalive_retries: None, tcp_nodelay: true, http2_keep_alive_interval: None, http2_keep_alive_timeout: None, @@ -111,6 +115,8 @@ impl Endpoint { init_stream_window_size: None, init_connection_window_size: None, tcp_keepalive: None, + tcp_keepalive_interval: None, + tcp_keepalive_retries: None, tcp_nodelay: true, http2_keep_alive_interval: None, http2_keep_alive_timeout: None, @@ -258,7 +264,6 @@ impl Endpoint { /// probes. /// /// Default is no keepalive (`None`) - /// pub fn tcp_keepalive(self, tcp_keepalive: Option) -> Self { Endpoint { tcp_keepalive, @@ -266,6 +271,31 @@ impl Endpoint { } } + /// Set the duration between two successive TCP keepalive retransmissions, + /// if acknowledgement to the previous keepalive transmission is not received. + /// + /// This is only used if `tcp_keepalive` is not None. + /// + /// Defaults to None, which is the system default. + pub fn tcp_keepalive_interval(self, tcp_keepalive_interval: Option) -> Self { + Endpoint { + tcp_keepalive_interval, + ..self + } + } + + /// Set the number of retransmissions to be carried out before declaring that remote end is not available. + /// + /// This is only used if `tcp_keepalive` is not None. + /// + /// Defaults to None, which is the system default. + pub fn tcp_keepalive_retries(self, tcp_keepalive_retries: Option) -> Self { + Endpoint { + tcp_keepalive_retries, + ..self + } + } + /// Apply a concurrency limit to each request. /// /// ``` @@ -428,6 +458,8 @@ impl Endpoint { http.enforce_http(false); http.set_nodelay(self.tcp_nodelay); http.set_keepalive(self.tcp_keepalive); + http.set_keepalive_interval(self.tcp_keepalive_interval); + http.set_keepalive_retries(self.tcp_keepalive_retries); http.set_connect_timeout(self.connect_timeout); http.set_local_address(self.local_address); self.connector(http) @@ -543,6 +575,16 @@ impl Endpoint { pub fn get_tcp_keepalive(&self) -> Option { self.tcp_keepalive } + + /// Get whether TCP keepalive interval. + pub fn get_tcp_keepalive_interval(&self) -> Option { + self.tcp_keepalive_interval + } + + /// Get whether TCP keepalive retries. + pub fn get_tcp_keepalive_retries(&self) -> Option { + self.tcp_keepalive_retries + } } impl From for Endpoint { diff --git a/tonic/src/transport/server/incoming.rs b/tonic/src/transport/server/incoming.rs index fb63d7480..3e03ce782 100644 --- a/tonic/src/transport/server/incoming.rs +++ b/tonic/src/transport/server/incoming.rs @@ -19,6 +19,9 @@ pub struct TcpIncoming { inner: TcpListenerStream, nodelay: Option, keepalive: Option, + keepalive_time: Option, + keepalive_interval: Option, + keepalive_retries: Option, } impl TcpIncoming { @@ -66,9 +69,42 @@ impl TcpIncoming { } /// Sets the `TCP_KEEPALIVE` option on the accepted connection. - pub fn with_keepalive(self, keepalive: Option) -> Self { - let keepalive = keepalive.map(|t| TcpKeepalive::new().with_time(t)); - Self { keepalive, ..self } + pub fn with_keepalive(self, keepalive_time: Option) -> Self { + Self { + keepalive_time, + keepalive: make_keepalive( + keepalive_time, + self.keepalive_interval, + self.keepalive_retries, + ), + ..self + } + } + + /// Sets the `TCP_KEEPINTVL` option on the accepted connection. + pub fn with_keepalive_interval(self, keepalive_interval: Option) -> Self { + Self { + keepalive_interval, + keepalive: make_keepalive( + self.keepalive_time, + keepalive_interval, + self.keepalive_retries, + ), + ..self + } + } + + /// Sets the `TCP_KEEPCNT` option on the accepted connection. + pub fn with_keepalive_retries(self, keepalive_retries: Option) -> Self { + Self { + keepalive_retries, + keepalive: make_keepalive( + self.keepalive_time, + self.keepalive_interval, + keepalive_retries, + ), + ..self + } } /// Returns the local address that this tcp incoming is bound to. @@ -83,6 +119,9 @@ impl From for TcpIncoming { inner: TcpListenerStream::new(listener), nodelay: None, keepalive: None, + keepalive_time: None, + keepalive_interval: None, + keepalive_retries: None, } } } @@ -121,6 +160,70 @@ fn set_accepted_socket_options( } } +fn make_keepalive( + keepalive_time: Option, + keepalive_interval: Option, + keepalive_retries: Option, +) -> Option { + let mut dirty = false; + let mut keepalive = TcpKeepalive::new(); + if let Some(t) = keepalive_time { + keepalive = keepalive.with_time(t); + dirty = true; + } + + #[cfg( + // See https://docs.rs/socket2/0.5.8/src/socket2/lib.rs.html#511-525 + any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "ios", + target_os = "visionos", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "tvos", + target_os = "watchos", + target_os = "windows", + ) + )] + if let Some(t) = keepalive_interval { + keepalive = keepalive.with_interval(t); + dirty = true; + } + + #[cfg( + // See https://docs.rs/socket2/0.5.8/src/socket2/lib.rs.html#557-570 + any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "ios", + target_os = "visionos", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "tvos", + target_os = "watchos", + ) + )] + if let Some(r) = keepalive_retries { + keepalive = keepalive.with_retries(r); + dirty = true; + } + + // avoid clippy errors for targets that do not use these fields. + let _ = keepalive_retries; + let _ = keepalive_interval; + + dirty.then_some(keepalive) +} + #[cfg(test)] mod tests { use crate::transport::server::TcpIncoming; diff --git a/tonic/src/transport/server/mod.rs b/tonic/src/transport/server/mod.rs index c5684e72d..ee978baa3 100644 --- a/tonic/src/transport/server/mod.rs +++ b/tonic/src/transport/server/mod.rs @@ -344,6 +344,8 @@ impl Server { /// specified will be the time to remain idle before sending TCP keepalive /// probes. /// + /// Important: This setting is only respected when not using `serve_with_incoming`. + /// /// Default is no keepalive (`None`) /// #[must_use]