Skip to content

Deadline is parsed but not enforced — handler runs to completion after Connect-Timeout-Ms lapses #92

@iainmcgin

Description

@iainmcgin

The runtime parses Connect-Timeout-Ms / grpc-timeout headers into RequestContext::deadline: Option<Instant>, but does not cancel the handler future when the deadline lapses. The handler must check ctx.deadline itself.

This differs from tonic and connect-go, both of which cancel the handler when the deadline passes (tonic drops the future; connect-go's context.Context is cancelled). A user who writes a slow handler and relies on the protocol timeout for protection will find their handler running to completion, holding DB connections, locks, and worker-pool slots, only for the response to be discarded because the client has already gone away. Under load this turns a slow downstream into a cascading resource exhaustion.

It's a defensible design choice — a handler may legitimately want to commit a transaction it has started even if the client gave up — but it's a surprising default and should at minimum be a documented, explicit choice.

Proposed direction

  • Default to enforcing the deadline: wrap the handler future in tokio::time::timeout(deadline - now, handler) and translate the timeout into a DeadlineExceeded error. This matches the behavior of every other framework in the comparison set and the gRPC spec's intent.
  • Add an opt-out: Server::enforce_deadlines(false) (and an equivalent for the axum/tower integration path, perhaps a RouterConfig knob) for services that need to outlive the caller.
  • Document the behavior and the opt-out in the guide's "Production hardening" section.
  • Consider exposing the remaining time (ctx.time_remaining() -> Option<Duration>) so handlers can budget downstream calls, similar to gRPC-Go's ctx.Deadline() idiom.

Open questions

  • For client/bidi streaming handlers, should the deadline apply to the whole stream lifetime or be resettable per message?
  • Whether cancellation should be "drop the future" (Rust idiom, may leak partial work) or "signal and grace period" (closer to Go).

Scope

Medium — touches the dispatcher hot path, needs careful testing against the conformance suite's timeout cases.

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