Skip to content

Fix HTTP/1 idle streamable response disconnect#225

Draft
samuel-williams-shopify wants to merge 1 commit into
mainfrom
gaia-autopr-http1-idle-stream-close
Draft

Fix HTTP/1 idle streamable response disconnect#225
samuel-williams-shopify wants to merge 1 commit into
mainfrom
gaia-autopr-http1-idle-stream-close

Conversation

@samuel-williams-shopify
Copy link
Copy Markdown
Contributor

@samuel-williams-shopify samuel-williams-shopify commented Jun 3, 2026

TL;DR

Fix HTTP/1.1 idle streamable response bodies so their body task is stopped when the client disconnects.

Context

Closes #224.

HTTP/1 normal responses consumed streamable bodies through body.read. For Protocol::HTTP::Body::Streamable, that schedules the body block in an internal fiber. If the body writes one chunk and then parks idle, the server waits for the next body.read and the producer fiber waits independently. A client disconnect with no pending application read/write can leave the producer fiber parked indefinitely.

Changes

  • Add a protocol-owned HTTP/1.1 streamable response path for normal chunked responses.
  • Write streamable response chunks through a small ChunkedOutput adapter instead of the generic body.read path.
  • Stop the protocol-owned body task from Server#closed, mirroring the HTTP/2 lifecycle more closely.
  • Add a transient read-side monitor only for HTTP/1.1 streamable responses, using the existing non-consuming readable? probe so request-body or pipelined data does not get treated as disconnect.
  • Leave non-streamable bodies, HEAD responses, HTTP/1.0, upgrades, and tunnels on their existing paths to avoid adding task overhead to ordinary responses.
  • Add a regression test that reads the initial SSE chunk, closes the raw HTTP/1.1 client socket while the body is idle, and asserts the body block unwinds.

Tophatting

  • bundle update
  • bundle exec sus test/protocol/http/body/streamable.rb
  • bundle exec sus test/async/http/protocol/http11.rb
  • bundle exec sus test/async/http/protocol/http11.rb test/protocol/http/body/streamable.rb
  • bundle exec bake test

HTTP/1 streamable response bodies were previously consumed through the generic body.read path for normal chunked responses. For Protocol::HTTP::Body::Streamable this hides the producing body block behind an internal scheduled fiber. If the body writes an initial chunk and then parks idle, the HTTP/1 server waits on the next body.read, the user body fiber waits independently, and a peer disconnect does not necessarily trigger another socket read or write. The body fiber can therefore remain parked indefinitely.

Handle normal HTTP/1.1 streamable responses with a protocol-owned body task instead. The task calls body.call(stream) directly and writes chunks through a small ChunkedOutput adapter, preserving chunked transfer framing while giving the HTTP/1 connection ownership of the task lifecycle. A transient monitor watches for peer close using the existing non-consuming readable? probe and closes the connection only when the socket is no longer readable, avoiding false positives for request-body data or pipelined bytes. Non-streamable bodies, HEAD responses, HTTP/1.0, upgrades, and tunnels continue using the existing paths so ordinary responses do not pay the extra task cost.

Add a regression test for issue #224 that opens an HTTP/1.1 streaming response, reads the first SSE chunk, closes the client socket while the body is idle, and asserts the body block unwinds.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP/1: idle streaming response body fiber is not cancelled on client disconnect (no parity with HTTP/2 closed(error) -> Output#stop)

1 participant