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

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.
This commit is contained in:
yhirose
2026-08-26 01:15:47 -04:00
committed by GitHub
parent 19352ae929
commit 84f75185fe
+8 -2
View File
@@ -13432,8 +13432,14 @@ inline bool Server::listen_internal() {
} else if (errno == EINTR || errno == EAGAIN) {
continue;
}
if (svr_sock_ != INVALID_SOCKET) {
detail::close_socket(svr_sock_);
// Take the descriptor out of svr_sock_ before closing it: a later
// stop() would otherwise shutdown()/close() a value the OS may have
// reused, and keep_alive() watches svr_sock_ to notice the server is
// gone. The exchange also settles the race with a concurrent stop(),
// since whichever side takes the descriptor closes it exactly once.
auto listen_sock = svr_sock_.exchange(INVALID_SOCKET);
if (listen_sock != INVALID_SOCKET) {
detail::close_socket(listen_sock);
ret = false;
output_error_log(Error::Connection, nullptr);
} else {