From 96dd647882fd04fb70f796552763b4fea2712047 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 13 Apr 2026 14:50:51 -0500 Subject: [PATCH] Fix heap blowout on TBeams (#10155) * Fix heap blowout on TBeams * Update src/graphics/draw/MessageRenderer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Set MESSAGE_HISTORY_LIMIT to 10 for original ESP32 to optimize RAM usage * Optimize message frame allocation to prevent excessive memory usage * Refine message history limits for resource-constrained builds and cap cached lines to prevent heap overflow * Update src/graphics/draw/MessageRenderer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/MessageStore.h | 7 +++++++ src/graphics/draw/MessageRenderer.cpp | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/MessageStore.h b/src/MessageStore.h index 6203d8ed0..77271f1c9 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -21,8 +21,15 @@ // How many messages are stored (RAM + flash). // Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage. #ifndef MESSAGE_HISTORY_LIMIT +#if defined(ARCH_ESP32) && \ + !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)) +// 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 #endif +#endif // Internal alias used everywhere in code – do NOT redefine elsewhere. #define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 501a7ae2c..2fd9bf541 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -422,6 +422,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 std::vector isMine; // track alignment std::vector isHeader; // track header lines std::vector ackForLine; + // Hard limit on total cached lines to prevent unbounded growth from a single long message. + // Reserve to the actual cache cap up front, because a single message can expand to many more + // wrapped display lines than a small per-message estimate would predict. 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_CACHED_LINES = 100U; // ~5-6KB for std::string overhead on 32-bit (if each ~50-60 bytes avg) + allLines.reserve(MAX_CACHED_LINES); + isMine.reserve(MAX_CACHED_LINES); + isHeader.reserve(MAX_CACHED_LINES); + ackForLine.reserve(MAX_CACHED_LINES); for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) { const auto &m = *it; @@ -565,16 +576,23 @@ 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) { - allLines.push_back(ln); + 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; } } // Cache lines and heights - cachedLines = allLines; + cachedLines.swap(allLines); cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); std::vector blocks = buildMessageBlocks(isHeader, isMine);