Reduce TLS memory requirements for KOReader sync
Split heap gate into separate free-memory (50KB) and largest-block (20KB) thresholds based on field measurements. Enable wolfSSL single-precision ECC to use fixed 256-bit arrays instead of heap-allocated fast-math bignums, reducing TLS handshake memory footprint. Reclaim ~7KB by right-sizing ESP timer task stacks and move WiFi code out of IRAM to free ~25-30KB for heap. Add memory audit landmarks for font and EPUB allocations.
This commit is contained in:
@@ -369,6 +369,11 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8
|
||||
}
|
||||
stats.pageBufferBytes += totalBytes;
|
||||
stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry);
|
||||
// MEMFIX-PORT: page-slot address landmark for the heap map; portable
|
||||
// Landmark for the heap block map: page slots are the largest flash-font
|
||||
// allocations and otherwise show up as anonymous ~4-20 KB used blocks.
|
||||
LOG_DBG("FDC", "page slot buffer=%p bytes=%u glyphs=%u", static_cast<void*>(slot.buffer), (unsigned)totalBytes,
|
||||
(unsigned)glyphCount);
|
||||
|
||||
slot.fontData = fontData;
|
||||
slot.glyphCount = glyphCount;
|
||||
|
||||
@@ -104,6 +104,7 @@ class SdCardFont {
|
||||
uint32_t uniqueGlyphs = 0;
|
||||
uint32_t bitmapBytes = 0;
|
||||
};
|
||||
// MEMFIX-PORT: SD font resident-bytes audit; portable
|
||||
// Log per-style resident heap (full tables + kept-if-fits mini arenas +
|
||||
// advance tables + overflow bitmaps) and return the total in bytes. Pure
|
||||
// accounting — no allocation, no state change.
|
||||
|
||||
@@ -32,6 +32,7 @@ class SdCardFontManager {
|
||||
// Get name of currently loaded family (empty if none).
|
||||
const std::string& currentFamilyName() const { return loadedFamilyName_; };
|
||||
|
||||
// MEMFIX-PORT: font manager audit passthrough; portable
|
||||
// Sum of loaded fonts' resident heap (see SdCardFont::reportMemory).
|
||||
size_t reportMemory() const;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ class Epub {
|
||||
}
|
||||
~Epub() = default;
|
||||
std::string& getBasePath() { return contentBasePath; }
|
||||
// MEMFIX-PORT: epub resident-bytes audit accessor; portable
|
||||
// Approximate resident heap of the open book (audit): path strings, the CSS
|
||||
// file list, and the parsed stylesheet. BookMetadataCache is file-backed
|
||||
// (counts + HalFile handles) and contributes little.
|
||||
|
||||
@@ -132,6 +132,7 @@ class Section {
|
||||
// (covers finalized sections and partials from a previous session).
|
||||
std::optional<uint16_t> findAnchor(const std::string& anchor) const;
|
||||
|
||||
// MEMFIX-PORT: section resident-bytes audit accessor; portable
|
||||
// Approximate resident heap for the audit log. Steady state (no build) a
|
||||
// Section holds little beyond itself; during a build the page LUT and path
|
||||
// strings dominate (the parser's internal footprint is not walked here).
|
||||
|
||||
@@ -67,6 +67,7 @@ class CssParser {
|
||||
*/
|
||||
[[nodiscard]] static CssStyle parseInlineStyle(std::string_view styleValue);
|
||||
|
||||
// MEMFIX-PORT: stylesheet resident-bytes audit accessor; portable
|
||||
// Approximate resident heap of the parsed stylesheet, for the audit log.
|
||||
// unordered_map cost model: bucket array + one node per rule (libstdc++ node
|
||||
// overhead ~= 2 pointers + hash) + key string capacity when it exceeds SSO.
|
||||
|
||||
@@ -22,7 +22,21 @@ constexpr char DEVICE_ID[] = "crosspoint-reader";
|
||||
// footprint is smaller than mbedTLS's old ~48KB peak, but keep a conservative
|
||||
// floor. Check both total free heap and largest contiguous block so fragmented
|
||||
// heap does not fall through into a failed TLS allocation path.
|
||||
constexpr uint32_t MIN_HEAP_FOR_TLS = 55000;
|
||||
// MEMFIX-PORT: TLS heap gate; portable
|
||||
// Field data (July 2026): launching sync from a reader session lands at
|
||||
// 51.9-58.2 KB free / 42-53 KB maxAlloc after WiFi comes up. wolfSSL handles
|
||||
// allocation failure by returning MEMORY_E (no abort under -fno-exceptions),
|
||||
// so an optimistic attempt degrades to the same clean "sync failed" as the
|
||||
// gate — the gate only needs to keep out states where a doomed handshake
|
||||
// would waste tens of seconds, not guarantee success.
|
||||
//
|
||||
// Free and largest-block have separate requirements: with SP ECC
|
||||
// (WOLFSSL_HAVE_SP_ECC) the handshake's crypto uses fixed 256-bit arrays, so
|
||||
// the largest single TLS allocation is the ~17 KB wolfSSL record buffer, not
|
||||
// a run of fast-math bignums. A handshake was measured succeeding inside a
|
||||
// 43 KB largest block; requiring 50 KB contiguous refused syncs that fit.
|
||||
constexpr uint32_t MIN_FREE_FOR_TLS = 50000;
|
||||
constexpr uint32_t MIN_BLOCK_FOR_TLS = 20000;
|
||||
|
||||
// Apply the shared KOSync auth headers after begin(). x-auth-* is the native
|
||||
// KOSync scheme; Basic auth is added for Calibre-Web-Automated compatibility.
|
||||
@@ -39,9 +53,9 @@ void applyAuthHeaders(freeink::SecureHttpClient& http) {
|
||||
bool insufficientHeap() {
|
||||
const uint32_t freeHeap = ESP.getFreeHeap();
|
||||
const uint32_t maxAllocHeap = ESP.getMaxAllocHeap();
|
||||
if (freeHeap < MIN_HEAP_FOR_TLS || maxAllocHeap < MIN_HEAP_FOR_TLS) {
|
||||
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free, %u max alloc (need %u)", freeHeap,
|
||||
maxAllocHeap, MIN_HEAP_FOR_TLS);
|
||||
if (freeHeap < MIN_FREE_FOR_TLS || maxAllocHeap < MIN_BLOCK_FOR_TLS) {
|
||||
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free (need %u), %u max alloc (need %u)", freeHeap,
|
||||
MIN_FREE_FOR_TLS, maxAllocHeap, MIN_BLOCK_FOR_TLS);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+23
-1
@@ -45,7 +45,14 @@ build_flags =
|
||||
-DWOLFSSL_OPTIONS_H
|
||||
-DWOLFSSL_CLIENT_EXAMPLE
|
||||
-DWOLFSSL_TLS13
|
||||
-DWOLFSSL_SP_RISCV32
|
||||
# MEMFIX-PORT: single-precision ECC (sp_c32.c). Without it every P-256 operation
|
||||
# (TLS 1.3 key_share keygen, ECDHE, ECDSA cert verify) runs on fast-math bignums
|
||||
# that WOLFSSL_SMALL_STACK heap-allocates at FP_MAX_BITS size -- tens of KB of
|
||||
# temporaries, which OOMs (MP_MEM) at the ~50KB free heap a reading session
|
||||
# leaves. SP uses fixed 256-bit arrays: a few KB, and several times faster.
|
||||
# SP_SMALL trades the large precomputed point tables for smaller flash.
|
||||
-DWOLFSSL_HAVE_SP_ECC
|
||||
-DWOLFSSL_SP_SMALL
|
||||
-DHAVE_TLS_EXTENSIONS
|
||||
-DHAVE_SUPPORTED_CURVES
|
||||
-DHAVE_HKDF
|
||||
@@ -99,6 +106,21 @@ custom_sdkconfig =
|
||||
CONFIG_BT_CTRL_BLE_MAX_ACT=3 ; was 6; need conn + scan + initiate only
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=50 ; was 100; pairing-time scan only
|
||||
CONFIG_BT_CTRL_ADV_DUP_FILT_MAX=10 ; was 30
|
||||
; MEMFIX-PORT: task stack right-sizing (~7 KB); NOTE develop has no
|
||||
; custom_sdkconfig block — port via sdkconfig.defaults or equivalent.
|
||||
; Task stack right-sizing from measured high-water marks (heap block map +
|
||||
; per-task stack audit, July 2026): esp_timer used ~0.8 KB of 8 KB across
|
||||
; every capture incl. BLE sessions; the FreeRTOS timer service used ~0.5 KB
|
||||
; of 4 KB. Neither runs TLS or app code. ~7 KB back to the heap.
|
||||
CONFIG_ESP_TIMER_TASK_STACK_SIZE=4096
|
||||
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2560
|
||||
; Move the WiFi stack's non-critical hot paths out of IRAM into flash.
|
||||
; On the C3, IRAM and DRAM share one SRAM pool, so the ~25-30 KB this
|
||||
; frees lands directly in the heap — paid for with lower WiFi throughput
|
||||
; during transfers (occasional sync/OTA use, not streaming: acceptable).
|
||||
; IRAM cost is static, so the heap gain applies even with WiFi off.
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=n
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=n
|
||||
; Keep the Arduino wrappers for the removed cloud components (below) out of
|
||||
; the core source list; all other bundled libraries default to enabled.
|
||||
CONFIG_ARDUINO_SELECTIVE_COMPILATION=y
|
||||
|
||||
@@ -12,8 +12,12 @@ OVERRIDES = f"""
|
||||
#ifndef HAVE_FFDHE_2048
|
||||
#define HAVE_FFDHE_2048
|
||||
#endif
|
||||
/* MEMFIX-PORT: 8192 handles up to RSA-4096 keys (the public-CA maximum,
|
||||
ISRG Root X1 included) with half the per-bignum heap of 16384: with
|
||||
WOLFSSL_SMALL_STACK each fast-math temp is FP_MAX_BITS/8 * 2 bytes on the
|
||||
heap, and TLS cert verification allocates dozens at once. */
|
||||
#undef FP_MAX_BITS
|
||||
#define FP_MAX_BITS 16384
|
||||
#define FP_MAX_BITS 8192
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "HeapMap.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Logging.h>
|
||||
#include <Memory.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "rom/ets_sys.h"
|
||||
|
||||
// heap_caps_dump walks each heap inside a critical section (interrupts
|
||||
// masked), so its output can neither go through the UART-0 ROM path we can't
|
||||
// see nor be flow-controlled toward the CDC (the CDC ring drains in an
|
||||
// interrupt handler — waiting deadlocks into the interrupt WDT,
|
||||
// field-verified). Instead the ROM putc parses each line into a compact
|
||||
// record; the captured table is logged after the dump with interrupts live.
|
||||
namespace heapmap {
|
||||
namespace {
|
||||
struct BlockRec {
|
||||
uint32_t addr;
|
||||
uint32_t size;
|
||||
bool free;
|
||||
};
|
||||
constexpr uint16_t kMaxRecs = 1400;
|
||||
BlockRec* g_recs = nullptr; // borrowed buffer, valid only during capture
|
||||
uint16_t g_recCount = 0;
|
||||
bool g_overflowed = false;
|
||||
char g_line[96];
|
||||
uint8_t g_lineLen = 0;
|
||||
|
||||
void captInterpolatePutc(char c) {
|
||||
if (c != '\n') {
|
||||
if (g_lineLen < sizeof(g_line) - 1) g_line[g_lineLen++] = c;
|
||||
return;
|
||||
}
|
||||
g_line[g_lineLen] = '\0';
|
||||
g_lineLen = 0;
|
||||
// e.g. "Block 0x3fcc69bc data, size: 89424 bytes, Free: Yes"
|
||||
unsigned addr = 0, size = 0;
|
||||
char freeWord[4] = {0};
|
||||
if (sscanf(g_line, "Block 0x%x data, size: %u bytes, Free: %3s", &addr, &size, freeWord) == 3) {
|
||||
if (g_recs && g_recCount < kMaxRecs) {
|
||||
g_recs[g_recCount++] = {addr, size, freeWord[0] == 'Y'};
|
||||
} else {
|
||||
g_overflowed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void dump() {
|
||||
auto recBuf = makeUniqueNoThrow<BlockRec[]>(kMaxRecs);
|
||||
if (!recBuf) {
|
||||
LOG_ERR("MEM", "heap map skipped: no room for capture buffer");
|
||||
return;
|
||||
}
|
||||
g_recs = recBuf.get();
|
||||
g_recCount = 0;
|
||||
g_overflowed = false;
|
||||
g_lineLen = 0;
|
||||
// Capture (interrupts masked inside the dump): parse into records, never
|
||||
// wait. Log the table afterward with the system live. NOTE: the capture
|
||||
// buffer itself appears in the map as a used block of ~17.4 KB — it frees
|
||||
// on return (observer effect, do not chase it as a leak/splitter).
|
||||
ets_install_putc1(&captInterpolatePutc);
|
||||
heap_caps_dump(MALLOC_CAP_8BIT);
|
||||
ets_install_uart_printf();
|
||||
g_recs = nullptr;
|
||||
|
||||
LOG_DBG("MEM", "---- heap block map: %u blocks%s ----", g_recCount, g_overflowed ? " (TRUNCATED)" : "");
|
||||
uint32_t dustCount = 0, dustBytes = 0;
|
||||
for (uint16_t i = 0; i < g_recCount; ++i) {
|
||||
const auto& r = recBuf[i];
|
||||
if (r.free || r.size >= 256) {
|
||||
LOG_DBG("MEM", "%s 0x%08x %u", r.free ? "FREE" : "used", r.addr, r.size);
|
||||
} else {
|
||||
dustCount++;
|
||||
dustBytes += r.size;
|
||||
}
|
||||
}
|
||||
LOG_DBG("MEM", "dust: %u used blocks < 256B totaling %u bytes", dustCount, dustBytes);
|
||||
LOG_DBG("MEM", "---- end heap block map ----");
|
||||
}
|
||||
} // namespace heapmap
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
// MEMFIX-PORT: heap block map (on-demand via CMD:MEMMAP + reader one-shot); portable, no BLE dependency
|
||||
namespace heapmap {
|
||||
// Capture-and-log the DRAM heap block map (address/size/free per block,
|
||||
// sub-256B used blocks rolled up as "dust"). Safe to call from the main loop;
|
||||
// ~60-100 LOG_DBG lines. See HeapMap.cpp for why capture-then-log is the only
|
||||
// shape that works (heap_caps_dump runs with interrupts masked).
|
||||
void dump();
|
||||
} // namespace heapmap
|
||||
@@ -32,6 +32,7 @@ class SdCardFontSystem {
|
||||
/// Non-const access to the registry (for FontInstaller).
|
||||
SdCardFontRegistry& registry() { return registry_; }
|
||||
|
||||
// MEMFIX-PORT: font system audit passthrough; portable
|
||||
/// Resident heap held by loaded SD fonts (audit; see SdCardFont::reportMemory).
|
||||
size_t reportFontMemory() const { return manager_.reportMemory(); }
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "SdCardFontSystem.h"
|
||||
#include <esp_heap_caps.h>
|
||||
#include "rom/ets_sys.h"
|
||||
#include "HeapMap.h"
|
||||
|
||||
namespace {
|
||||
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
|
||||
@@ -70,45 +71,6 @@ bool isInReadFolder(const std::string& path) {
|
||||
return path.size() > n && path.compare(0, n, READ_FOLDER) == 0 && path[n] == '/';
|
||||
}
|
||||
|
||||
// Heap block map capture. heap_caps_dump walks each heap inside a critical
|
||||
// section (interrupts masked), so its output can neither go through the
|
||||
// UART-0 ROM path we can't see nor be flow-controlled toward the CDC (the CDC
|
||||
// ring drains in an interrupt handler — waiting deadlocks into the interrupt
|
||||
// WDT, field-verified). Instead the ROM putc parses each line into a compact
|
||||
// record; the captured table is logged after the dump with interrupts live.
|
||||
namespace heapmap {
|
||||
struct BlockRec {
|
||||
uint32_t addr;
|
||||
uint32_t size;
|
||||
bool free;
|
||||
};
|
||||
constexpr uint16_t kMaxRecs = 1400;
|
||||
BlockRec* recs = nullptr; // borrowed buffer, valid only during capture
|
||||
uint16_t recCount = 0;
|
||||
bool overflowed = false;
|
||||
char line[96];
|
||||
uint8_t lineLen = 0;
|
||||
|
||||
void putc(char c) {
|
||||
if (c != '\n') {
|
||||
if (lineLen < sizeof(line) - 1) line[lineLen++] = c;
|
||||
return;
|
||||
}
|
||||
line[lineLen] = '\0';
|
||||
lineLen = 0;
|
||||
// e.g. "Block 0x3fcc69bc data, size: 89424 bytes, Free: Yes"
|
||||
unsigned addr = 0, size = 0;
|
||||
char freeWord[4] = {0};
|
||||
if (sscanf(line, "Block 0x%x data, size: %u bytes, Free: %3s", &addr, &size, freeWord) == 3) {
|
||||
if (recs && recCount < kMaxRecs) {
|
||||
recs[recCount++] = {addr, size, freeWord[0] == 'Y'};
|
||||
} else {
|
||||
overflowed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace heapmap
|
||||
|
||||
class FrameBufferBuildLoan {
|
||||
public:
|
||||
explicit FrameBufferBuildLoan(GfxRenderer& renderer) : renderer_(renderer) {}
|
||||
@@ -1545,6 +1507,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
// preceding lines show (mini rebuilds, kern reloads, BLE churn).
|
||||
LOG_DBG("MEM", "post-render: free=%u maxAlloc=%u", (unsigned)ESP.getFreeHeap(), (unsigned)ESP.getMaxAllocHeap());
|
||||
{
|
||||
// MEMFIX-PORT: per-render heap owner audit + one-shot block map; portable
|
||||
// Heap audit: attribute resident heap to its owners so margin work is
|
||||
// measurement-driven. `other` = IDF/Arduino baseline + SdFat + epub
|
||||
// metadata + BLE (when on) + anything not yet instrumented.
|
||||
@@ -1568,36 +1531,10 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
static bool heapMapDumped = false;
|
||||
if (!heapMapDumped && !(section && section->isBuilding())) {
|
||||
heapMapDumped = true;
|
||||
auto recBuf = makeUniqueNoThrow<heapmap::BlockRec[]>(heapmap::kMaxRecs);
|
||||
if (recBuf) {
|
||||
heapmap::recs = recBuf.get();
|
||||
heapmap::recCount = 0;
|
||||
heapmap::overflowed = false;
|
||||
heapmap::lineLen = 0;
|
||||
// Capture (interrupts masked inside the dump): parse into records,
|
||||
// never wait. Log the table afterward with the system live.
|
||||
ets_install_putc1(&heapmap::putc);
|
||||
heap_caps_dump(MALLOC_CAP_8BIT);
|
||||
ets_install_uart_printf();
|
||||
heapmap::recs = nullptr;
|
||||
|
||||
LOG_DBG("MEM", "---- heap block map: %u blocks%s ----", heapmap::recCount,
|
||||
heapmap::overflowed ? " (TRUNCATED)" : "");
|
||||
uint32_t dustCount = 0, dustBytes = 0;
|
||||
for (uint16_t i = 0; i < heapmap::recCount; ++i) {
|
||||
const auto& r = recBuf[i];
|
||||
if (r.free || r.size >= 256) {
|
||||
LOG_DBG("MEM", "%s 0x%08x %u", r.free ? "FREE" : "used", r.addr, r.size);
|
||||
} else {
|
||||
dustCount++;
|
||||
dustBytes += r.size;
|
||||
}
|
||||
}
|
||||
LOG_DBG("MEM", "dust: %u used blocks < 256B totaling %u bytes", dustCount, dustBytes);
|
||||
LOG_DBG("MEM", "---- end heap block map ----");
|
||||
} else {
|
||||
LOG_ERR("MEM", "heap map skipped: no room for capture buffer");
|
||||
}
|
||||
// Landmarks: known owners' addresses, so map blocks self-identify.
|
||||
LOG_DBG("MEM", "landmark framebuffer=%p section=%p epub=%p activity=%p", renderer.getFrameBuffer(),
|
||||
static_cast<void*>(section.get()), static_cast<void*>(epub.get()), static_cast<void*>(this));
|
||||
heapmap::dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "images/LoadingIcon.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
#include "util/ScreenshotUtil.h"
|
||||
#include <Memory.h>
|
||||
#include "HeapMap.h"
|
||||
|
||||
GfxRenderer renderer(display);
|
||||
MappedInputManager mappedInputManager(gpio, renderer);
|
||||
@@ -596,6 +598,23 @@ void loop() {
|
||||
LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes, MaxAlloc: %d bytes", ESP.getFreeHeap(),
|
||||
ESP.getHeapSize(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
|
||||
lastMemPrint = millis();
|
||||
// MEMFIX-PORT: per-task stack high-water audit; portable
|
||||
// Task stack audit (~once/min): name, stack base (matches blocks in the
|
||||
// heap map), and high-water free bytes — the margin available for
|
||||
// right-sizing each stack. TRACE_FACILITY is on in this sdkconfig.
|
||||
static uint8_t memPrintCount = 0;
|
||||
if (++memPrintCount >= 6) {
|
||||
memPrintCount = 0;
|
||||
const UBaseType_t taskCount = uxTaskGetNumberOfTasks();
|
||||
auto taskStatus = makeUniqueNoThrow<TaskStatus_t[]>(taskCount + 2);
|
||||
if (taskStatus) {
|
||||
const UBaseType_t got = uxTaskGetSystemState(taskStatus.get(), taskCount + 2, nullptr);
|
||||
for (UBaseType_t i = 0; i < got; ++i) {
|
||||
LOG_DBG("MEM", "task %-20s stackBase=%p highWaterFree=%u", taskStatus[i].pcTaskName,
|
||||
taskStatus[i].pxStackBase, (unsigned)taskStatus[i].usStackHighWaterMark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming serial commands,
|
||||
@@ -611,6 +630,11 @@ void loop() {
|
||||
uint8_t* buf = display.getFrameBuffer();
|
||||
logSerial.write(buf, bufferSize);
|
||||
logSerial.printf("SCREENSHOT_END\n");
|
||||
// MEMFIX-PORT: on-demand heap map serial command; portable
|
||||
} else if (cmd == "MEMMAP") {
|
||||
// On-demand heap block map: capture the heap exactly when it looks
|
||||
// interesting (e.g. maxAlloc degraded mid-session) without a reboot.
|
||||
heapmap::dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,14 @@ void CrossPointWebServer::begin() {
|
||||
udpActive = udp.begin(LOCAL_UDP_PORT);
|
||||
LOG_DBG("WEB", "Discovery UDP %s on port %d", udpActive ? "enabled" : "failed", LOCAL_UDP_PORT);
|
||||
|
||||
// All request handlers run on the task that calls handleClient(). Register
|
||||
// that task before any handler can call esp_task_wdt_reset().
|
||||
const esp_err_t watchdogResult = esp_task_wdt_add(nullptr);
|
||||
watchdogTaskRegistered = watchdogResult == ESP_OK;
|
||||
if (!watchdogTaskRegistered) {
|
||||
LOG_ERR("WEB", "Failed to register web server task with watchdog: %s", esp_err_to_name(watchdogResult));
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
LOG_DBG("WEB", "Web server started on port %d", port);
|
||||
@@ -230,6 +238,10 @@ void CrossPointWebServer::abortWsUpload(const char* tag) {
|
||||
void CrossPointWebServer::stop() {
|
||||
if (!running || !server) {
|
||||
LOG_DBG("WEB", "stop() called but already stopped (running=%d, server=%p)", running, server.get());
|
||||
if (watchdogTaskRegistered) {
|
||||
esp_task_wdt_delete(nullptr);
|
||||
watchdogTaskRegistered = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,6 +282,11 @@ void CrossPointWebServer::stop() {
|
||||
LOG_DBG("WEB", "Web server stopped and deleted");
|
||||
LOG_DBG("WEB", "[MEM] Free heap after delete server: %d bytes", ESP.getFreeHeap());
|
||||
|
||||
if (watchdogTaskRegistered) {
|
||||
esp_task_wdt_delete(nullptr);
|
||||
watchdogTaskRegistered = false;
|
||||
}
|
||||
|
||||
// Note: Static upload variables (uploadFileName, uploadPath, uploadError) are declared
|
||||
// later in the file and will be cleared when they go out of scope or on next upload
|
||||
LOG_DBG("WEB", "[MEM] Free heap final: %d bytes", ESP.getFreeHeap());
|
||||
|
||||
@@ -72,6 +72,7 @@ class CrossPointWebServer {
|
||||
std::unique_ptr<WebServer> server = nullptr;
|
||||
std::unique_ptr<WebSocketsServer> wsServer = nullptr;
|
||||
bool running = false;
|
||||
bool watchdogTaskRegistered = false;
|
||||
bool apMode = false; // true when running in AP mode, false for STA mode
|
||||
uint16_t port = 80;
|
||||
uint16_t wsPort = 81; // WebSocket port
|
||||
|
||||
Reference in New Issue
Block a user