Skip to content

Commit 79554e8

Browse files
committed
fix: don't destroy server in stop() - causes use-after-free
The stop() method was calling m_server.reset() which destroys the httplib::Server. But listen() is a blocking call that may still be on the call stack in another thread. Destroying the server while listen() is returning causes null pointer dereference (SIGSEGV at 0x20). The crash was in httplib's process_request during map operations (__construct_node at string:1793), where httplib was trying to copy headers but the Server object had been destroyed. Fix: - stop() now only signals shutdown via m_server->stop(), does NOT call reset() or clear() - Destructor handles actual destruction: stops server, resets (joins threads), then clears content - This ensures the server is only destroyed after listen() has fully returned and all threads are joined
1 parent e9cf580 commit 79554e8

1 file changed

Lines changed: 13 additions & 9 deletions

File tree

src/odr/http_server.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ class HttpServer::Impl {
9191
m_server->stop();
9292
m_server.reset(); // Destroy server, join all thread pool threads
9393
}
94+
95+
// Clear content after server is fully destroyed.
96+
// This is safe now because all threads have been joined.
97+
clear();
98+
9499
// Now safe to let other members destruct - no threads are running
95100
}
96101

@@ -239,21 +244,20 @@ class HttpServer::Impl {
239244
ODR_VERBOSE(*m_logger, "Stopping HTTP server...");
240245

241246
// Set stopping flag first to reject new requests immediately.
242-
// This prevents new requests from starting while we're shutting down.
243247
m_stopping.store(true, std::memory_order_release);
244248

245249
if (m_server) {
246-
// Stop the server to prevent new connections.
247-
// Note: httplib::Server::stop() signals shutdown but thread pool
248-
// threads may still be running. They only fully stop when the
249-
// Server is destroyed. For explicit stop() calls (not destructor),
250-
// we destroy the server here to ensure threads are joined.
250+
// Signal the server to stop. This will cause listen() to return.
251+
// IMPORTANT: Do NOT call m_server.reset() here! The listen() call
252+
// may still be on the call stack in another thread. Destroying the
253+
// server while listen() is returning causes use-after-free crashes.
254+
// The destructor will handle the actual destruction after listen()
255+
// has fully returned.
251256
m_server->stop();
252-
m_server.reset(); // Destroy server, join all thread pool threads
253257
}
254258

255-
// Clear content after server is fully destroyed to avoid use-after-free.
256-
clear();
259+
// Note: We don't call clear() here anymore because handlers might still
260+
// be running until the server is actually destroyed in the destructor.
257261
}
258262

259263
private:

0 commit comments

Comments
 (0)