Skip to content

Keep alive is slowing down shutdown #1959

@cschreib-ibex

Description

@cschreib-ibex

Problem

We have an application running an HTTP server, which also ships web pages which regularly send GET requests to the server, to update the page content.

The application often needs to be "soft-restarted" (the application's process stays alive, but most of the classes are destroyed and re-created, including the HTTP server). When this happens, we are seeing long delays in shutting down the HTTP server, up to a few seconds.

I have traced it down to the "keep alive" logic, which possibly waits for the full duration configured in CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND before giving up and letting the server shutdown. I believe the issue is essentially that the "keep alive" wait cannot be interrupted by the shutdown:

cpp-httplib/httplib.h

Lines 3256 to 3271 in 131bc6c

process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
size_t keep_alive_max_count,
time_t keep_alive_timeout_sec, T callback) {
assert(keep_alive_max_count > 0);
auto ret = false;
auto count = keep_alive_max_count;
while (svr_sock != INVALID_SOCKET && count > 0 &&
select_read(sock, keep_alive_timeout_sec, 0) > 0) {
auto close_connection = count == 1;
auto connection_closed = false;
ret = callback(close_connection, connection_closed);
if (!ret || connection_closed) { break; }
count--;
}
return ret;
}

Here, select_read() will block for up to keep_alive_timeout_sec, even when the server socket is closed, because the condition svr_sock != INVALID_SOCKET is only checked before starting the call to select_read(), and not inside.

A possibly better implementation:

inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
                       time_t keep_alive_timeout_sec) {
  using namespace std::chrono;
  const auto start = steady_clock::now();
  while (svr_sock != INVALID_SOCKET &&
         (steady_clock::now() - start) < seconds(keep_alive_timeout_sec)) {
    if (select_read(sock, 0, 10000) > 0) {
      return true;
    }
  }

  return false;
}

template <typename T>
inline bool
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
                           size_t keep_alive_max_count,
                           time_t keep_alive_timeout_sec, T callback) {
  assert(keep_alive_max_count > 0);
  auto ret = false;
  auto count = keep_alive_max_count;
  while (keep_alive(svr_sock, sock, keep_alive_timeout_sec) && count > 0) {
    auto close_connection = count == 1;
    auto connection_closed = false;
    ret = callback(close_connection, connection_closed);
    if (!ret || connection_closed) { break; }
    count--;
  }
  return ret;
}

I see there used to be a keep_alive() function that looked similar (but still wasn't checking for svr_sock), and that was removed in 978a4f6. I lack the context to understand why that was done, but the implementation above passed all the tests.

How to reproduce

  • Compile the following C++ code.
  • Start the executable.

Output I get:

shutdown time: 5003 ms

C++ code:

#include "httplib.h"

#include <thread>
#include <chrono>

int main() {
    using namespace std::chrono;

    // Set up server listning in a thread.
    httplib::Server server;
    server.bind_to_port("0.0.0.0", 8080);

    server.set_mount_point("/", ".");

    // Wait until server is ready.
    auto thread = std::thread([&] { server.listen_after_bind(); });
    server.wait_until_ready();

    // Make a single client request, with keep alive enabled.
    httplib::Client client("127.0.0.1", 8080);
    client.set_keep_alive(true);
    client.Get("/index.html");

    // Stop.
    auto start = steady_clock::now();
    server.stop();
    thread.join();
    auto end = steady_clock::now();
    std::cout << "shutdown time: " << duration_cast<milliseconds>(end - start).count() << " ms" << std::endl;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions