Skip to content

Add HTTP/2 server keepalive ping (with_http2_keepalive_interval/timeout) #175

Description

@iainmcgin

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions