Skip to content

Do not give up the queue because one accept failed - #790

Merged
SimonCropp merged 3 commits into
mainfrom
fix-listener-dies-on-peer-reset
Aug 22, 2026
Merged

Do not give up the queue because one accept failed#790
SimonCropp merged 3 commits into
mainfrom
fix-listener-dies-on-peer-reset

Conversation

@SimonCropp

Copy link
Copy Markdown
Member

Listen returned on any SocketException from Accept, without looking at the error
code or at cancellation. That treats a failure of one accept as a failure of the
listener, and the two are not the same thing at all.

A peer that resets while its connection is still sitting in the backlog surfaces
exactly this way - WSAECONNRESET on Windows, ECONNABORTED on BSD and macOS - and
is common enough that Kestrel retries it by name. The consequence here is worse
than a dropped connection: the socket stays bound, so nobody else can take the
queue for the life of the process, and every later client lands in a backlog
that nothing is draining. PiperServer already continues in the same situation.

Return only when cancelled, or on OperationAborted and Interrupted, which are
how a stopped listener reports itself when the token has not been observed yet.

The tests pin the rule rather than the race. I wrote an end to end one first -
twenty abortive closes, then a real exchange - and deleted it after confirming
it passes with the old return still in place: on Windows the accept succeeds
and the reset surfaces later, during the read, which a different catch already
handles. A test that cannot fail is worse than no test.

Listen returned on any SocketException from Accept, without looking at the error
code or at cancellation. That treats a failure of one accept as a failure of the
listener, and the two are not the same thing at all.

A peer that resets while its connection is still sitting in the backlog surfaces
exactly this way - WSAECONNRESET on Windows, ECONNABORTED on BSD and macOS - and
is common enough that Kestrel retries it by name. The consequence here is worse
than a dropped connection: the socket stays bound, so nobody else can take the
queue for the life of the process, and every later client lands in a backlog
that nothing is draining. PiperServer already continues in the same situation.

Return only when cancelled, or on OperationAborted and Interrupted, which are
how a stopped listener reports itself when the token has not been observed yet.

The tests pin the rule rather than the race. I wrote an end to end one first -
twenty abortive closes, then a real exchange - and deleted it after confirming
it passes with the old `return` still in place: on Windows the accept succeeds
and the reset surfaces later, during the read, which a different catch already
handles. A test that cannot fail is worse than no test.
@SimonCropp SimonCropp added this to the 20.0.0 milestone Aug 22, 2026
@SimonCropp
SimonCropp merged commit 5a5c616 into main Aug 22, 2026
8 checks passed
@SimonCropp
SimonCropp deleted the fix-listener-dies-on-peer-reset branch August 22, 2026 01:54
This was referenced Aug 26, 2026
This was referenced Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant