Skip to content

Clear svr_sock_ before closing it on the accept loop's fatal path - #2560

Merged
yhirose merged 1 commit into
masterfrom
fix-accept-fatal-path-svr-sock
Aug 26, 2026
Merged

Clear svr_sock_ before closing it on the accept loop's fatal path#2560
yhirose merged 1 commit into
masterfrom
fix-accept-fatal-path-svr-sock

Conversation

@yhirose

@yhirose yhirose commented Aug 26, 2026

Copy link
Copy Markdown
Owner

When accept() failed for a reason the retry branches do not cover, the loop closed svr_sock_ but left the descriptor in the atomic. Two things go wrong from there:

  • A later stop() reads the stale value and calls shutdown()/close() on it. By then the OS may have reused the descriptor for an unrelated socket (a worker's keep-alive connection, or one the application opened), and that connection is torn down instead.
  • keep_alive() in the worker threads watches svr_sock_ to notice that the server is going away, so the workers keep waiting on a listening socket that no longer exists.

Take the descriptor with exchange(INVALID_SOCKET) before closing it, which is what stop() already does. That also settles the race with a concurrent stop(): whichever side takes the descriptor closes it exactly once, and the other sees INVALID_SOCKET and does nothing.

There is no portable way to force accept() into the fatal path from a test, so this comes without one.

Supersedes #2555, which reported the same bug and fixes it the same way.

When accept() failed for a reason the retry branches do not cover, the
loop closed svr_sock_ but left the descriptor in the atomic. Two things
go wrong from there:

- A later stop() reads the stale value and calls shutdown()/close() on
  it. By then the OS may have reused the descriptor for an unrelated
  socket (a worker's keep-alive connection, or one the application
  opened), and that connection is torn down instead.
- keep_alive() in the worker threads watches svr_sock_ to notice that
  the server is going away, so the workers keep waiting on a listening
  socket that no longer exists.

Take the descriptor with exchange(INVALID_SOCKET) before closing it,
which is what stop() already does. That also settles the race with a
concurrent stop(): whichever side takes the descriptor closes it exactly
once, and the other sees INVALID_SOCKET and does nothing.
@yhirose
yhirose merged commit 84f7518 into master Aug 26, 2026
44 of 45 checks passed
@yhirose
yhirose deleted the fix-accept-fatal-path-svr-sock branch August 26, 2026 05:20
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