Both Clyde North observers stopped answering over WiFi after days of uptime while LoRa and MQTT kept working. `memory` showed heap_min ~1KB and a largest internal block of 19KB: the HTTPS listener was alive but no mbedTLS handshake (~40KB contiguous) could be allocated. The trigger was the Aug 9 lru_purge change: with a 2-socket pool, every browser page-load burst evicted a live session and forced a fresh TLS handshake, and the repeated 40KB alloc/free cycles fragmented internal RAM. Before that change the pool simply jammed, so nothing churned. - Send `Connection: close` and trigger a session close on one-shot responses (pages, favicon, redirect, login, 401s) so page-load bursts release their sockets immediately. The authenticated /api/* polling connection keeps keep-alive so it does not pay a handshake per poll. - Gate WebPanelServer::start() on internal heap headroom (56KB free / 32KB largest), retrying every 15s instead of every loop tick. - Self-heal: when the panel is idle and the largest internal block drops below 24KB, stop and re-create the server to return its pools. - Count the MQTT teardown path that deliberately abandons a client on a heap-integrity failure. - Expose the above as `heals:`/`deferred:` in `get web.status` and `leaked:` in `get mqtt.status`. - Fix the stats page Channel/Gateway cells showing `--`: the /api/stats summary JSON never carried channel, gateway health, or the watchdog count (only the `get wifi.status` string did). - Add eastmesh-tools/web-heap-check.sh to read heap/service health over the API and optionally stress the panel with browser-style bursts. - Release notes 2026.8.3 for both observer tracks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
49 lines
1.3 KiB
C++
49 lines
1.3 KiB
C++
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
#include <helpers/IdentityStore.h>
|
|
#include <helpers/NetworkStateProvider.h>
|
|
|
|
#include "WebPanelServer.h"
|
|
#include "WebPrefs.h"
|
|
|
|
class WebService {
|
|
public:
|
|
WebService();
|
|
|
|
void begin(FILESYSTEM* fs);
|
|
void end();
|
|
void loop();
|
|
void suspendForOTA();
|
|
void prepareForOTAStart();
|
|
|
|
void setCommandRunner(WebPanelCommandRunner* runner);
|
|
void setNetworkStateProvider(NetworkStateProvider* network) { _network = network; }
|
|
|
|
bool setWebEnabled(bool enabled);
|
|
bool isWebEnabled() const { return _prefs.web_enabled != 0; }
|
|
bool setWebStatsEnabled(bool enabled);
|
|
bool isWebStatsEnabled() const { return _prefs.web_stats_enabled != 0; }
|
|
|
|
void formatWebStatusReply(char* reply, size_t reply_size) const;
|
|
bool isPanelRunning() const { return _panel.isRunning(); }
|
|
bool isPanelUnlocked() const { return _panel.hasSessionToken(); }
|
|
|
|
private:
|
|
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
|
|
void ensureWebServer();
|
|
void healIfStarved(unsigned long now_ms);
|
|
#endif
|
|
bool savePrefs();
|
|
|
|
FILESYSTEM* _fs;
|
|
WebPrefs _prefs;
|
|
WebPanelCommandRunner* _runner;
|
|
NetworkStateProvider* _network;
|
|
WebPanelServer _panel;
|
|
bool _suspended_for_ota;
|
|
unsigned long _last_start_attempt_ms;
|
|
unsigned long _last_heal_ms;
|
|
unsigned long _last_heal_check_ms;
|
|
};
|