From 5928f32364543ee29ddac83d4175aa4b2eb16032 Mon Sep 17 00:00:00 2001 From: spfenwick Date: Sat, 2 May 2026 16:01:45 +1200 Subject: [PATCH] Avoid OOM diring HTML parsing by reducing max block size and freeing unneeded buffers --- lib/Epub/Epub/ParsedText.cpp | 9 ++++++++- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp | 11 ++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index 61a6a210..bc869102 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -355,12 +355,19 @@ void ParsedText::layoutAndExtractLines( } } - // Remove consumed words so size() reflects only remaining words + // Remove consumed words so size() reflects only remaining words, then + // release excess capacity. Without shrink_to_fit the vector retains a + // large allocation from before the flush; the next paragraph fills it + // back up and eventually needs an even larger contiguous realloc. if (lineCount > 0) { const size_t consumed = lineBreakIndices[lineCount - 1]; words.erase(words.begin(), words.begin() + consumed); wordStyles.erase(wordStyles.begin(), wordStyles.begin() + consumed); wordContinues.erase(wordContinues.begin(), wordContinues.begin() + consumed); + words.shrink_to_fit(); + wordStyles.shrink_to_fit(); + wordContinues.shrink_to_fit(); + isContinuation_ = !includeLastLine; } } diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index bfa5ad9a..dfe7d664 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -1207,11 +1207,12 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char self->partWordBuffer[self->partWordBufferIndex++] = s[i]; } - // If we have > 750 words buffered up, perform the layout and consume out all but the last line - // There should be enough here to build out 1-2 full pages and doing this will free up a lot of - // memory. - // Spotted when reading Intermezzo, there are some really long text blocks in there. - if (self->currentTextBlock->size() > 750) { + // Flush when words approach the doubling boundary that would require a + // large contiguous realloc. 96 fires before capacity reaches 128 + // (the next doubling after 64), keeping the realloc below 1.5KB and + // releasing excess capacity via shrink_to_fit in layoutAndExtractLines. + // The original 750-word threshold was too late for low-heap devices. + if (self->currentTextBlock->size() > 96) { LOG_DBG("EHP", "Text block too long, splitting into multiple pages"); const int horizontalInset = self->currentTextBlock->getBlockStyle().totalHorizontalInset(); const uint16_t effectiveWidth = (horizontalInset < self->viewportWidth)