fix: pre-flight heap floor before section builds; no inline BLE restart
Layout code (line-break DP arrays, CSS lookups, glyph buffers) allocates via std::vector/std::string and abort()s on OOM under -fno-exceptions -- it cannot fail cleanly mid-build. Field crashes (X4, BLE resident): builds entered at ~11 KB free and aborted in ParsedText:: computeLineBreaks; an inline BLE restart after recovery re-starved the render and abort()ed in FontCacheManager's scanText_.reserve at ~8 KB. - BUILD_MIN_FREE_HEAP (40 KB): pre-flight before the build; below the floor go straight to recovery instead of attempting a doomed build - Recovery no longer restarts NimBLE inline; the lifecycle stays paused until the render completes (scoped unpause guard), then the main-loop lifecycle restarts BLE behind its own heap gate
This commit is contained in:
@@ -795,6 +795,15 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The build-recovery path below frees the BLE stack and pauses the main-loop lifecycle
|
||||||
|
// so it can't restart NimBLE while this render is still allocating (glyph prewarm, scan
|
||||||
|
// strings -- an inline restart re-starved exactly that and abort()ed). Unpause only
|
||||||
|
// when the render fully completes, on every exit path; the main-loop lifecycle then
|
||||||
|
// brings BLE back and shows its reconnect popup.
|
||||||
|
struct LifecycleUnpause {
|
||||||
|
~LifecycleUnpause() { bleinput::setLifecyclePaused(false); }
|
||||||
|
} lifecycleUnpause;
|
||||||
|
|
||||||
const auto showPendingSyncSaveError = [this]() {
|
const auto showPendingSyncSaveError = [this]() {
|
||||||
if (!pendingSyncSaveError) return;
|
if (!pendingSyncSaveError) return;
|
||||||
pendingSyncSaveError = false;
|
pendingSyncSaveError = false;
|
||||||
@@ -865,24 +874,33 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
SETTINGS.imageRendering, SETTINGS.focusReadingEnabled, popupFn);
|
SETTINGS.imageRendering, SETTINGS.focusReadingEnabled, popupFn);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool built = buildSection();
|
// The layout code (line-break DP arrays, CSS lookups, glyph buffers) allocates freely
|
||||||
|
// and abort()s on OOM under -fno-exceptions, so a starved heap must be handled BEFORE
|
||||||
|
// the build: pre-flight the floor and take the recovery path up front instead of
|
||||||
|
// crashing mid-parse. Field data: builds succeed at ~46 KB free with BLE resident;
|
||||||
|
// abort() observed at ~11 KB free.
|
||||||
|
const bool heapTooLow = ESP.getFreeHeap() < BUILD_MIN_FREE_HEAP;
|
||||||
|
if (heapTooLow) {
|
||||||
|
LOG_ERR("ERS", "Pre-build heap %u below floor %u; entering build recovery", (unsigned)ESP.getFreeHeap(),
|
||||||
|
(unsigned)BUILD_MIN_FREE_HEAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool built = !heapTooLow && buildSection();
|
||||||
if (!built && SETTINGS.bluetoothEnabled) {
|
if (!built && SETTINGS.bluetoothEnabled) {
|
||||||
// Building a section needs a large contiguous inflate (deflate) window that the
|
// Building a section needs a large contiguous inflate (deflate) window that the
|
||||||
// resident NimBLE stack fragments out of existence (~16 KB max block with BT on).
|
// resident NimBLE stack fragments out of existence (~16 KB max block with BT on).
|
||||||
// Free the BLE stack, build, then restore it. The chapter is cached afterwards, so
|
// Free the BLE stack and retry. The chapter is cached afterwards, so this recovery
|
||||||
// this recovery runs at most once per uncached chapter; BT reconnects in a few s.
|
// runs at most once per uncached chapter.
|
||||||
LOG_INF("ERS", "Section build failed with Bluetooth on; freeing BLE RAM and retrying");
|
LOG_INF("ERS", "Section build needs heap; freeing BLE RAM and retrying");
|
||||||
bleinput::setLifecyclePaused(true);
|
bleinput::setLifecyclePaused(true);
|
||||||
bleinput::stop();
|
bleinput::stop();
|
||||||
built = buildSection();
|
built = buildSection();
|
||||||
const bool bleOk = bleinput::ensureStarted();
|
// Deliberately do NOT restart BLE inline: this render still has its own allocations
|
||||||
bleinput::setLifecyclePaused(false);
|
// to make (glyph prewarm, scan strings), and an inline NimBLE restart re-starves
|
||||||
LOG_INF("ERS", "BLE restart after build: begin=%d", bleOk);
|
// it -- field crash: std::string::reserve(2048) abort()ed at ~8 KB free right
|
||||||
// Hold the "BT Connecting..." popup until the remote re-links, then force the
|
// after an inline restart. The lifecycle stays paused until this render fully
|
||||||
// page render below onto the ghost-cleanup (HALF) path so the popup clears
|
// completes (unpause guard at the top of render()); the main-loop lifecycle then
|
||||||
// without ghosting the grayscale page.
|
// restarts BLE behind its own heap gate and shows the reconnect popup.
|
||||||
bleinput::showConnectingUntilLinked(renderer, mappedInput);
|
|
||||||
requestGhostCleanup();
|
|
||||||
}
|
}
|
||||||
if (!built && !bootWasSilentRestart()) {
|
if (!built && !bootWasSilentRestart()) {
|
||||||
// Even with BLE freed, the build can fail when this session's parse churn has
|
// Even with BLE freed, the build can fail when this session's parse churn has
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ class EpubReaderActivity final : public Activity {
|
|||||||
SavedPosition savedPositions[MAX_FOOTNOTE_DEPTH] = {};
|
SavedPosition savedPositions[MAX_FOOTNOTE_DEPTH] = {};
|
||||||
int footnoteDepth = 0;
|
int footnoteDepth = 0;
|
||||||
|
|
||||||
|
// Heap floor for entering a section build. The layout code allocates freely (line-break DP
|
||||||
|
// arrays sized by word count, CSS rule lookups, glyph buffers) and under -fno-exceptions an
|
||||||
|
// OOM there abort()s the firmware instead of failing cleanly -- so a starved heap must be
|
||||||
|
// handled *before* the build, not after. Field data: builds succeed at ~46 KB free with BLE
|
||||||
|
// resident; abort() observed at ~11 KB free. CSS styling already degrades below 48 KB
|
||||||
|
// (MIN_FREE_HEAP_FOR_CSS), so 40 KB trades a few early BLE teardowns for not crashing.
|
||||||
|
static constexpr size_t BUILD_MIN_FREE_HEAP = 40 * 1024;
|
||||||
|
|
||||||
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
||||||
int orientedMarginBottom, int orientedMarginLeft);
|
int orientedMarginBottom, int orientedMarginLeft);
|
||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user