mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-04 03:18:10 +10:00
fix(http): keep reaping open TLS connections under low heap so the heap can recover (#11539)
* fix(http): keep reaping open TLS connections under low heap so the heap can recover Once free heap dropped below MIN_HEAP_FOR_SSL (40 KB) with HTTPS connections open, the node's heap never came back and every later HTTPS or TCP-API connection failed until a reset - node alive, on WiFi, unusable. handleWebResponse() skipped secureServer->loop() entirely under low heap so no new TLS handshake would be attempted on a heap that can't hold its context. But HTTPServer::loop() is the only place already-accepted connections are serviced and reaped: its first pass calls ->loop() on each open one (where the 20 s idle timeout and the SSL close-notify state machine run) and deletes the closed ones. Skipping the whole loop froze the up-to-MAX_HTTPS_CONNECTIONS TLS sessions already open. Never looped, they never timed out, their mbedTLS contexts and pbufs were never freed, so free heap never climbed back over 40 KB, so the loop was skipped forever. The guard's own precondition was what kept it from clearing. Split the two halves. Under low heap keep driving and reaping the connections we already hold, and only skip the accept. HTTPServer keeps its connection table protected, so a thin MeshHTTPSServer subclass exposes serviceExistingConnections(), the first half of HTTPServer::loop() verbatim. Log line reworded to say what now happens: not accepting, not skipping. Verified on a Heltec V3 (Endor AP) against a control build with #11537 (so the node survives the squeeze instead of aborting first): - Recipe: held sockets on 80/4403 + pending TLS, 100 s of HTTPS pokes, repeat. Control: Low heap pins at 6-17 KB, HTTPS dead, and 3 min after all pressure is released heap is still ~12 KB with Low heap firing every 30 s - permanent until reset. Fix: never dips under 40 KB, both pressure rounds 3/3, 65 KB after. - Branch driven deliberately (verify-only heap hog pinning free heap at ~28 KB with a real idle TLS session held open): under the guard the fix logs open=1 -> reaped=1 at the 20 s idle timeout, and heap goes 26 -> 65 KB before the hog is even released. On the control logic that session stays frozen for the whole window. Fixes #11538. * fix(http): trim the low-heap comments to the two-line guideline The mechanism is in the commit message and PR; the source keeps the one-line why. No code change. (CodeRabbit)
This commit is contained in:
@@ -62,8 +62,33 @@ static const uint8_t MAX_HTTPS_CONNECTIONS = 2;
|
||||
// Minimum free heap required for SSL handshake (~40KB for mbedTLS contexts)
|
||||
static const uint32_t MIN_HEAP_FOR_SSL = 40000;
|
||||
|
||||
// HTTPSServer that can service and reap the connections it already holds without accepting new ones,
|
||||
// so a low-heap pause doesn't freeze open TLS sessions (and their heap) in place. Needs the protected table.
|
||||
class MeshHTTPSServer : public HTTPSServer
|
||||
{
|
||||
public:
|
||||
using HTTPSServer::HTTPSServer;
|
||||
|
||||
/// The first half of HTTPServer::loop(): drive and reap existing connections, accept nothing.
|
||||
void serviceExistingConnections()
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
for (uint8_t i = 0; i < _maxConnections; i++) {
|
||||
if (!_connections[i])
|
||||
continue;
|
||||
if (_connections[i]->isClosed()) {
|
||||
delete _connections[i];
|
||||
_connections[i] = nullptr;
|
||||
} else {
|
||||
_connections[i]->loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static SSLCert *cert;
|
||||
static HTTPSServer *secureServer;
|
||||
static MeshHTTPSServer *secureServer;
|
||||
static HTTPServer *insecureServer;
|
||||
|
||||
volatile bool isWebServerReady;
|
||||
@@ -80,10 +105,12 @@ static void handleWebResponse()
|
||||
if (freeHeap >= MIN_HEAP_FOR_SSL) {
|
||||
secureServer->loop();
|
||||
} else {
|
||||
// Skip HTTPS when memory is low to prevent SSL setup failures
|
||||
// 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("Low heap (%u bytes), skipping HTTPS processing", freeHeap);
|
||||
LOG_WARN("Low heap (%u bytes), not accepting HTTPS connections", freeHeap);
|
||||
lastHeapWarning = millis();
|
||||
}
|
||||
}
|
||||
@@ -231,7 +258,7 @@ void initWebServer()
|
||||
LOG_DEBUG("Init Web Server");
|
||||
|
||||
// We can now use the new certificate to setup our server as usual.
|
||||
secureServer = new HTTPSServer(cert, 443, MAX_HTTPS_CONNECTIONS);
|
||||
secureServer = new MeshHTTPSServer(cert, 443, MAX_HTTPS_CONNECTIONS);
|
||||
insecureServer = new HTTPServer();
|
||||
|
||||
registerHandlers(insecureServer, secureServer);
|
||||
|
||||
Reference in New Issue
Block a user