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?
Interrupted HTTP/2 requests currently leave their streams open:
Async::HTTP::Protocol::HTTP2::Client#callcreates a stream, but does not cancel it ifwrite_requestorread_responseis interrupted by a timeout orAsync::Stop.These abandoned streams consume the peer’s
SETTINGS_MAX_CONCURRENT_STREAMSallowance and can eventually cause healthy requests to fail.I have a local fix that sends
RST_STREAM(CANCEL)from anensureblock. 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:
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?