Skip to content

Probe idle HTTP/1 connections before reuse - #230

Merged
ioquatix merged 7 commits into
mainfrom
gaia-autopr-probe-idle-http1
Jul 27, 2026
Merged

Probe idle HTTP/1 connections before reuse#230
ioquatix merged 7 commits into
mainfrom
gaia-autopr-probe-idle-http1

Conversation

@samuel-williams-shopify

Copy link
Copy Markdown
Contributor

TL;DR

Probe idle HTTP/1 connections before the pool reuses them, so a peer shutdown that is already observable does not turn a non-idempotent request into an EOFError.

Context

An underlying socket can be readable because EOF or a TLS close_notify is pending. The existing Readable#readable? check intentionally does not consume the layered transport, so it cannot distinguish that state from a connection on which another read may eventually succeed.

io-stream 0.14 introduced Readable#peek_partial, which can perform one non-blocking read through TLS while preserving any application bytes in the stream buffer. HTTP/1 can safely use that primitive while idle because it has no concurrent reader.

Closes #223

Changes

  • require io-stream ~> 0.14
  • use peek_partial(1) in the HTTP/1 connection viability check
  • reject application bytes that arrive while the HTTP/1 connection is idle
  • treat EOF, TLS shutdown, connection resets, and other transport failures observed by the probe as non-viable
  • leave HTTP/2 viability unchanged because its background reader owns transport reads
  • cover idle/open TLS, clean close_notify, abrupt TLS disconnect, pending buffered application data, inactive connections, and absent streams
  • add an end-to-end regression where a server closes a pooled TLS connection and a subsequent non-idempotent POST succeeds on a fresh connection

Tophatting

The end-to-end regression was also run against the previous viable? implementation and reproduced the reported EOFError from Protocol::HTTP1::Connection#read_response_line.

Validation on Ruby 3.4.4:

  • bundle exec sus test/async/http/ssl.rb test/async/http/protocol/http1/connection.rb — 8 tests, 12 assertions
  • bundle exec bake test — 236 passed, 3 skipped, 34,728 assertions
  • bundle exec rubocop — 127 files, no offenses
  • bundle exec bake decode:index:coverage lib — 292/292 public definitions documented
  • coverage run exercised every executable line added to viable?

Use IO::Stream::Readable#peek_partial to process pending EOF and TLS close notifications before the connection pool reuses an idle HTTP/1 connection. Reject unexpected application bytes on an idle connection and treat transport failures during the probe as non-viable.

Raise the io-stream dependency to 0.14 and cover clean TLS shutdown, abrupt disconnects, pending application data, inactive connections, missing streams, and an end-to-end non-idempotent POST after the server closes the pooled connection.

Closes #223
@samuel-williams-shopify
samuel-williams-shopify marked this pull request as ready for review July 27, 2026 05:53
Comment thread lib/async/http/protocol/http1/connection.rb Outdated
@ioquatix
ioquatix merged commit 3c8bec5 into main Jul 27, 2026
34 of 40 checks passed
@ioquatix
ioquatix deleted the gaia-autopr-probe-idle-http1 branch July 27, 2026 06:29
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.

Persistent connection issues for non-idempotent requests

2 participants