From f4f8188d2e9e3fcb0c338f4e483b93362f56d487 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 5 Oct 2026 06:30:44 +0200 Subject: [PATCH] fix(http): keep shutdown independent of logging callbacks --- CHANGELOG.md | 3 +++ src/odr/http_server.cpp | 6 +----- test/src/http_server_test.cpp | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae16cdf17..d097f937b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ The release run heads these entries with the version and opens a fresh ## Unreleased +- HTTP server shutdown avoids user logging callbacks, so logging failures + cannot interrupt cleanup or terminate the process during destruction. + - `DecodedFile::file_meta`, `identify`, and position `to_string` methods no longer promise `noexcept`; allocation failures can propagate instead of terminating the process. diff --git a/src/odr/http_server.cpp b/src/odr/http_server.cpp index ae58e0687..442b33987 100644 --- a/src/odr/http_server.cpp +++ b/src/odr/http_server.cpp @@ -273,8 +273,6 @@ class HttpServer::Impl : public std::enable_shared_from_this { return; } - ODR_VERBOSE(m_logger, "Stopping HTTP server..."); - // rejects requests in flight, and any listen() that has not started yet m_stopping.store(true, std::memory_order_release); m_bound = false; @@ -298,11 +296,9 @@ class HttpServer::Impl : public std::enable_shared_from_this { // the accept loop stands on the server and on what the handlers capture, // so neither may go before listen() has returned m_listen_done.wait(lock, [this] { return m_listening == 0; }); + m_content.clear(); } - // nothing is serving any more - clear(); - // ~server here, which joins the thread pool } diff --git a/test/src/http_server_test.cpp b/test/src/http_server_test.cpp index 9e3e985ec..3dabd3cc5 100644 --- a/test/src/http_server_test.cpp +++ b/test/src/http_server_test.cpp @@ -49,6 +49,24 @@ class ListenGuard { } // namespace +TEST(HttpServer, cleanup_does_not_call_user_loggers) { + class ThrowingLogger final : public ILogger { + public: + bool will_log(LogLevel) const override { + throw std::runtime_error("logging failed"); + } + void log(Time, LogLevel, const std::string &, + const std::source_location &) override {} + void flush() override {} + }; + const Logger logger(std::make_shared()); + { + const HttpServer server({}, logger); + EXPECT_NO_THROW(server.stop()); + } + EXPECT_NO_THROW({ const HttpServer server({}, logger); }); +} + TEST(HttpServer, bind_reports_the_port_it_got) { const HttpServer server;