From fc2316b2922e546d9e7770eb40a0b8997bcb0bd0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 13 Apr 2026 11:06:30 -0500 Subject: [PATCH] Refine message history limits for resource-constrained builds and cap cached lines to prevent heap overflow --- src/MessageStore.h | 3 ++- src/graphics/draw/MessageRenderer.cpp | 25 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/MessageStore.h b/src/MessageStore.h index 934c8b824..77271f1c9 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -23,7 +23,8 @@ #ifndef MESSAGE_HISTORY_LIMIT #if defined(ARCH_ESP32) && \ !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)) -// Original ESP32 has no PSRAM; keep fewer messages in RAM +// Baseline ESP32 (non-PSRAM variants) has limited heap; reduce message history on resource-constrained builds. +// Override with -DMESSAGE_HISTORY_LIMIT=N if needed. #define MESSAGE_HISTORY_LIMIT 10 #else #define MESSAGE_HISTORY_LIMIT 20 diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index f63994a89..fa85ceacf 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -422,17 +422,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 std::vector isMine; // track alignment std::vector isHeader; // track header lines std::vector ackForLine; - // Reserve based on filtered count but cap at the configured message limit × a - // worst-case lines-per-message (1 header + up to 4 body lines). This prevents - // a large single allocation — and the persistent capacity that swap() transfers - // into cachedLines — from growing beyond what the store can ever hold. + // Hard limit on total cached lines to prevent unbounded growth from a single long message. + // Reserve based on filtered count but cap at the configured message limit × a worst-case + // lines-per-message. For a display rendering only ~5-30 lines at a time, caching more than + // this limit wastes heap. Stop appending once we reach MAX_CACHED_LINES to prevent a single + // message from blowing out the heap. constexpr size_t MAX_LINES_PER_MSG = 5U; + constexpr size_t MAX_CACHED_LINES = 100U; // ~5-6KB for std::string overhead on 32-bit (if each ~50-60 bytes avg) const size_t estimatedLines = std::min(filtered.size() * MAX_LINES_PER_MSG, static_cast(MAX_MESSAGES_SAVED) * MAX_LINES_PER_MSG); - allLines.reserve(estimatedLines); - isMine.reserve(estimatedLines); - isHeader.reserve(estimatedLines); - ackForLine.reserve(estimatedLines); + allLines.reserve(std::min(estimatedLines, MAX_CACHED_LINES)); + isMine.reserve(std::min(estimatedLines, MAX_CACHED_LINES)); + isHeader.reserve(std::min(estimatedLines, MAX_CACHED_LINES)); + ackForLine.reserve(std::min(estimatedLines, MAX_CACHED_LINES)); for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) { const auto &m = *it; @@ -576,11 +578,18 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int wrapWidth = mine ? rightTextWidth : leftTextWidth; std::vector wrapped = generateLines(display, "", msgText, wrapWidth); + // Per-message wrap-line limit: even if wrapping produces many lines, cap them to prevent + // a single long message from consuming most or all of the cache. + constexpr size_t MAX_WRAPPED_LINES_PER_MSG = 20U; + size_t wrappedCount = 0; for (auto &ln : wrapped) { + if (allLines.size() >= MAX_CACHED_LINES || wrappedCount >= MAX_WRAPPED_LINES_PER_MSG) + break; // Cache limit or per-message limit reached; stop adding lines from this message allLines.emplace_back(std::move(ln)); isMine.push_back(mine); isHeader.push_back(false); ackForLine.push_back(AckStatus::NONE); + ++wrappedCount; } }