Summary
Add server-side HTTP/2 keepalive PING configuration to Server / BoundServer so the server can detect dead or half-open client connections and reclaim them.
impl Server { // and BoundServer
/// Interval between HTTP/2 keepalive PING frames sent on an idle connection.
/// Disabled by default.
pub fn with_http2_keepalive_interval(self, interval: Duration) -> Self { ... }
/// How long to wait for a PING ack before closing the connection.
/// Only effective when an interval is set. Defaults to 20s.
pub fn with_http2_keepalive_timeout(self, timeout: Duration) -> Self { ... }
}
Motivation
Every other gRPC server stack offers server-initiated h2 keepalive for dead-peer detection on long-lived streams; we currently offer none:
- grpc-go
keepalive.ServerParameters.Time / Timeout — default 2h / 20s
- grpc-java
keepAliveTime / keepAliveTimeout — default 2h / 20s
- tonic
http2_keepalive_interval / http2_keepalive_timeout — default off / 20s
- x/net/http2
ReadIdleTimeout / PingTimeout (used by connect-go)
This matters specifically for gRPC: long-lived server-streaming/bidi connections that go silent (NAT timeout, client crash, network partition) otherwise sit half-open, consuming a task and FD until the OS TCP timeout (which can be hours).
There is no escape hatch for this short of hand-rolling a hyper accept loop — axum::serve does not expose it either (it wraps the same hyper_util auto builder with defaults). So it belongs on our server.
Implementation
hyper's http2 builder exposes keep_alive_interval(Option<Duration>) and keep_alive_timeout(Duration) directly, so this is a passthrough on the AutoBuilder we already construct in serve_with_listener — no per-connection lifecycle logic needed.
Acceptance
- Both methods on
Server and BoundServer, disabled by default (interval unset).
- Default timeout 20s (matching the ecosystem) once an interval is set.
- Threaded through to the hyper http2 builder in the serve path.
- Tests asserting a configured interval results in PING frames / a dead connection being closed after the timeout.
Context: server-config audit comparing connectrpc-rs against tonic/grpc-go/grpc-java/connect-go/connect-es.
Summary
Add server-side HTTP/2 keepalive PING configuration to
Server/BoundServerso the server can detect dead or half-open client connections and reclaim them.Motivation
Every other gRPC server stack offers server-initiated h2 keepalive for dead-peer detection on long-lived streams; we currently offer none:
keepalive.ServerParameters.Time/Timeout— default 2h / 20skeepAliveTime/keepAliveTimeout— default 2h / 20shttp2_keepalive_interval/http2_keepalive_timeout— default off / 20sReadIdleTimeout/PingTimeout(used by connect-go)This matters specifically for gRPC: long-lived server-streaming/bidi connections that go silent (NAT timeout, client crash, network partition) otherwise sit half-open, consuming a task and FD until the OS TCP timeout (which can be hours).
There is no escape hatch for this short of hand-rolling a hyper accept loop —
axum::servedoes not expose it either (it wraps the samehyper_utilauto builder with defaults). So it belongs on our server.Implementation
hyper's
http2builder exposeskeep_alive_interval(Option<Duration>)andkeep_alive_timeout(Duration)directly, so this is a passthrough on theAutoBuilderwe already construct inserve_with_listener— no per-connection lifecycle logic needed.Acceptance
ServerandBoundServer, disabled by default (interval unset).Context: server-config audit comparing connectrpc-rs against tonic/grpc-go/grpc-java/connect-go/connect-es.