Refine message history limits for resource-constrained builds and cap cached lines to prevent heap overflow

This commit is contained in:
Ben Meadors
2026-04-13 11:06:30 -05:00
parent 263ed1d0ce
commit fc2316b292
2 changed files with 19 additions and 9 deletions
+2 -1
View File
@@ -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
+17 -8
View File
@@ -422,17 +422,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
std::vector<bool> isMine; // track alignment
std::vector<bool> isHeader; // track header lines
std::vector<AckStatus> 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<size_t>(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<std::string> 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;
}
}