Summary
Add with_max_connection_idle to Server / BoundServer: gracefully close a connection (GOAWAY, then the existing grace period) once it has had no in-flight requests for a configured duration.
impl Server { // and BoundServer
/// Retire a connection that has been idle (no in-flight requests) for
/// `duration`, by sending GOAWAY and then draining over the
/// max-connection-age grace period. Disabled by default.
pub fn with_max_connection_idle(self, duration: Duration) -> Self { ... }
}
Motivation
This complements the time-based with_max_connection_age (#165): age caps total lifetime regardless of use, idle reclaims connections that have gone quiet (clients behind NAT, bursty workloads, pooled clients holding connections they no longer need). It's part of the standard server connection-management set:
- grpc-go
keepalive.ServerParameters.MaxConnectionIdle (default infinity / disabled)
- grpc-java
maxConnectionIdle (default disabled)
- x/net/http2
http2.Server.IdleTimeout (used by connect-go)
- Traefik
respondingTimeouts.idleTimeout (default 180s)
No axum::serve escape hatch exists for it.
Implementation
Unlike keepalive/max-concurrent-streams, hyper does not offer a built-in idle-then-GOAWAY for h2, so this needs logic in the per-connection ConnectionLifecycle state machine added in #165:
- Track last-activity (reset an idle timer whenever a request starts / the connection has >0 active streams; arm it when streams drop to zero).
- When the idle timer fires, take the same
graceful_shutdown() + grace path the age timer takes.
- Interacts with
max_connection_age (whichever fires first retires the connection) and whole-server graceful shutdown (still drains indefinitely).
Detecting "active stream count" may need a hook at the tower service_fn dispatch boundary (increment on call, decrement on completion) since hyper doesn't surface per-connection stream counts directly.
Acceptance
with_max_connection_idle(d) on both types, disabled by default.
- Idle connection receives GOAWAY after
d of no in-flight requests, then force-closes after the grace period.
- A connection with steady traffic is never retired by the idle timer.
- Correct interaction with
max_connection_age and graceful shutdown; tests for each.
Context: server-config audit comparing connectrpc-rs against tonic/grpc-go/grpc-java/connect-go/connect-es.
Summary
Add
with_max_connection_idletoServer/BoundServer: gracefully close a connection (GOAWAY, then the existing grace period) once it has had no in-flight requests for a configured duration.Motivation
This complements the time-based
with_max_connection_age(#165): age caps total lifetime regardless of use, idle reclaims connections that have gone quiet (clients behind NAT, bursty workloads, pooled clients holding connections they no longer need). It's part of the standard server connection-management set:keepalive.ServerParameters.MaxConnectionIdle(default infinity / disabled)maxConnectionIdle(default disabled)http2.Server.IdleTimeout(used by connect-go)respondingTimeouts.idleTimeout(default 180s)No
axum::serveescape hatch exists for it.Implementation
Unlike keepalive/max-concurrent-streams, hyper does not offer a built-in idle-then-GOAWAY for h2, so this needs logic in the per-connection
ConnectionLifecyclestate machine added in #165:graceful_shutdown()+ grace path the age timer takes.max_connection_age(whichever fires first retires the connection) and whole-server graceful shutdown (still drains indefinitely).Detecting "active stream count" may need a hook at the tower
service_fndispatch boundary (increment on call, decrement on completion) since hyper doesn't surface per-connection stream counts directly.Acceptance
with_max_connection_idle(d)on both types, disabled by default.dof no in-flight requests, then force-closes after the grace period.max_connection_ageand graceful shutdown; tests for each.Context: server-config audit comparing connectrpc-rs against tonic/grpc-go/grpc-java/connect-go/connect-es.