From 132d3f0859c19492cb3e5cc25c2e2437aedf87f8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 16 Sep 2026 06:36:40 -0500 Subject: [PATCH] fix(http): probe the TLS heap only when a client is waiting for a slot The probe holds ~21 kB across two callocs and ran whenever a slot was free, which on a board with nobody connecting is every 50 ms while the web server is active and every second when it is idle - on exactly the low-heap boards this branch is for. It now runs only when a zero-timeout select() on the listen socket says a client is already in the backlog, which is the question loop() asks a few microseconds later. With nothing knocking the server drives its open connections and accepts nothing, so a connection is never accepted past an unmeasured gate; one that lands between the two select()s waits a tick. --- src/mesh/http/WebServer.cpp | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index dd3aebe3fa..8f163b497e 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -133,6 +133,19 @@ class MeshHTTPSServer : public HTTPSServer _connections[i]->loop(); } } + + /// loop()'s own accept test, asked microseconds earlier: without it the probe holds 21 kB on + /// every tick of a server nobody is talking to. + bool hasPendingConnection() const + { + if (_socket < 0) + return false; + fd_set sockfds; + FD_ZERO(&sockfds); + FD_SET(_socket, &sockfds); + timeval immediate = {}; + return select(_socket + 1, &sockfds, nullptr, nullptr, &immediate) > 0; + } }; static SSLCert *cert; @@ -149,21 +162,25 @@ static void handleWebResponse() if (isWebServerReady) { // Check heap before HTTPS processing - SSL requires significant memory if (secureServer) { - // Reap first so the probe sees the heap a finished session just returned. With every slot - // busy loop() cannot accept, so the probe would buy nothing. - const TlsHeapVerdict verdict = secureServer->reapClosedConnections() ? judgeTlsSessionHeap() : TlsHeapVerdict::Ok; - if (verdict == TlsHeapVerdict::Ok) { - secureServer->loop(); - } else { - // Low heap: accept nothing new, but keep servicing open connections so they can time out - // and free their contexts - skipping them pins the heap below the threshold for good. + // Reap first so the probe sees the heap a finished session just returned. Nowhere to put + // a connection, or nobody knocking: either way the probe would buy nothing. + if (!secureServer->reapClosedConnections() || !secureServer->hasPendingConnection()) { secureServer->serviceExistingConnections(); - static uint32_t lastHeapWarning = 0; - if (lastHeapWarning == 0 || !Throttle::isWithinTimespanMs(lastHeapWarning, 30000)) { - LOG_WARN("%s for a TLS session (%u free), not accepting HTTPS connections", - verdict == TlsHeapVerdict::TooLittleFree ? "Too little heap" : "No contiguous block", - (unsigned)heap_caps_get_free_size(TLS_HEAP_CAPS)); - lastHeapWarning = Time::stampMillis(); + } else { + const TlsHeapVerdict verdict = judgeTlsSessionHeap(); + if (verdict == TlsHeapVerdict::Ok) { + secureServer->loop(); + } else { + // Low heap: accept nothing new, but keep servicing open connections so they can time out + // and free their contexts - skipping them pins the heap below the threshold for good. + secureServer->serviceExistingConnections(); + static uint32_t lastHeapWarning = 0; + if (lastHeapWarning == 0 || !Throttle::isWithinTimespanMs(lastHeapWarning, 30000)) { + LOG_WARN("%s for a TLS session (%u free), not accepting HTTPS connections", + verdict == TlsHeapVerdict::TooLittleFree ? "Too little heap" : "No contiguous block", + (unsigned)heap_caps_get_free_size(TLS_HEAP_CAPS)); + lastHeapWarning = Time::stampMillis(); + } } } }