Skip to content

Http: a request that timed out on a pooled socket is not sent again - #147

Merged
bjmeetsfo merged 1 commit into
mainfrom
oss/pooled-socket-replay-safety
Aug 23, 2026
Merged

Http: a request that timed out on a pooled socket is not sent again#147
bjmeetsfo merged 1 commit into
mainfrom
oss/pooled-socket-replay-safety

Conversation

@bjmeetsfo

Copy link
Copy Markdown
Collaborator

The keep-alive pool reconnects and sends the request again whenever a pooled socket
fails. For the case it was written for that is exactly right: the server reaps idle
sockets, the next exchange on one fails immediately with a reset or a broken pipe or
an EOF, nothing was ever served on it, and reconnecting is transparent.

It was doing the same thing after a timeout, and a timeout is not that. The peer
accepted the request and did not answer in time. It may have processed it. Sending
it again on a fresh socket applies a write twice.

This sits one layer below the routing guard that just stopped re-sending writes of
unknown outcome, and it would have undone that guard from underneath: the routing
layer can decline to retry all it likes while the transport quietly retries for it.

Now a pooled exchange that fails with a timeout returns the error. Every other
failure still reconnects and re-sends, so the reaped-socket path -- the reason the
fallback exists -- is untouched.

Deliberately applied to all requests rather than only writes. The transport does not
know a write from a read, and threading that through every caller would put the
decision in more places than can be kept honest. Reads lose nothing that matters:
the routing layer above already refreshes and retries a read on any backend failure,
so the recovery is still there, one layer up, where it can see what it is retrying.

Measured by counting what the server received: the request arrived twice before this
change and arrives once after. The test fails with "left: 2, right: 1" if the guard
is removed.

The keep-alive pool reconnects and sends the request again whenever a pooled socket
fails. For the case it was written for that is exactly right: the server reaps idle
sockets, the next exchange on one fails immediately with a reset or a broken pipe or
an EOF, nothing was ever served on it, and reconnecting is transparent.

It was doing the same thing after a timeout, and a timeout is not that. The peer
accepted the request and did not answer in time. It may have processed it. Sending
it again on a fresh socket applies a write twice.

This sits one layer below the routing guard that just stopped re-sending writes of
unknown outcome, and it would have undone that guard from underneath: the routing
layer can decline to retry all it likes while the transport quietly retries for it.

Now a pooled exchange that fails with a timeout returns the error. Every other
failure still reconnects and re-sends, so the reaped-socket path -- the reason the
fallback exists -- is untouched.

Deliberately applied to all requests rather than only writes. The transport does not
know a write from a read, and threading that through every caller would put the
decision in more places than can be kept honest. Reads lose nothing that matters:
the routing layer above already refreshes and retries a read on any backend failure,
so the recovery is still there, one layer up, where it can see what it is retrying.

Measured by counting what the server received: the request arrived twice before this
change and arrives once after. The test fails with "left: 2, right: 1" if the guard
is removed.
@bjmeetsfo
bjmeetsfo requested a review from superhaiou as a code owner August 23, 2026 00:03
@bjmeetsfo
bjmeetsfo merged commit b6dd024 into main Aug 23, 2026
6 checks passed
@bjmeetsfo
bjmeetsfo deleted the oss/pooled-socket-replay-safety branch August 23, 2026 00:03
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.

1 participant