Преглед изворни кода

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.
yhirose пре 3 дана
родитељ
комит
84f75185fe
1 измењених фајлова са 8 додато и 2 уклоњено
  1. 8 2
      httplib.h

+ 8 - 2
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 {