Skip to content

How should HTTP/2 request cancellation handle a blocked writer? #252

Description

@ekmartin

Interrupted HTTP/2 requests currently leave their streams open: Async::HTTP::Protocol::HTTP2::Client#call creates a stream, but does not cancel it if write_request or read_response is interrupted by a timeout or Async::Stop.

These abandoned streams consume the peer’s SETTINGS_MAX_CONCURRENT_STREAMS allowance and can eventually cause healthy requests to fail.

I have a local fix that sends RST_STREAM(CANCEL) from an ensure block. The late-response-header issue this exposed was addressed in socketry/protocol-http2#33.

However, I haven’t opened a new PR because I’m unsure about the right behavior when cancellation itself blocks.

If another request is uploading to a peer that has stopped reading, sending the reset can block behind the shared writer—even after the original request timeout has fired. I reproduced this with a socket pair and a 1 MiB upload.

My current implementation gives the reset 100 ms to complete, then closes the connection while preserving the original exception. That bounds cleanup, but introduces a policy decision:

  • Temporary backpressure lasting over 100 ms closes the connection and fails other in-flight requests.
  • Waiting indefinitely defeats the caller’s timeout.
  • Abandoning a reset after a partial write can leave the connection unsafe to reuse.
  • Returning immediately and scheduling cancellation in the background requires managing cleanup tasks and accounting for streams that still occupy capacity on the peer.

The 100 ms threshold feels arbitrary, and I’m reluctant to introduce that behavior without discussing it first.

What cancellation behavior would you prefer here? Is bounded synchronous cleanup with connection closure an acceptable approach, or should cancellation be managed by the connection in the background with a separate deadline?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions