From 84f75185feb29050e829771ef792fdbb0a26bccc Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 26 Aug 2026 01:15:47 -0400 Subject: [PATCH] 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. --- httplib.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/httplib.h b/httplib.h index 282a1b05..572531b0 100644 --- a/httplib.h +++ b/httplib.h @@ -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 {