From a1428288094decd53ef35ee3fa056dcd3d347c7f Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:02:23 -0700 Subject: [PATCH] refactor(renderer): remove a transposition trap on the lazy-chunk failure path exhaustedRecoveryFailure ended in two adjacent booleans with opposite consequences: transposing them would have returned the raw SyntaxError and silently restored the crash this branch fixes, with no test able to catch it (the only call site passed true for both). The isChunkFailure parameter saved one regex scan on a path that only runs after a 10s reload wait, so drop it. Also evict recordedExhaustionKeys oldest-first instead of clearing wholesale, matching the breadcrumb and renderer-error key stores the comment cites, so an overflow cannot re-open the entire set to a repeat burst. --- src/renderer/src/lib/lazy-with-retry.ts | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/lib/lazy-with-retry.ts b/src/renderer/src/lib/lazy-with-retry.ts index f6c57cdeae4..b58fdbadea6 100644 --- a/src/renderer/src/lib/lazy-with-retry.ts +++ b/src/renderer/src/lib/lazy-with-retry.ts @@ -151,19 +151,23 @@ function exhaustedRecoveryFailure( outcome: string, // Set when the caller already recorded a vetoed breadcrumb for this outcome; // the 30-entry ring is the only diagnostic left, so it must not self-evict. - alreadyRecorded = false, - isChunkFailure = isKnownDynamicImportFailure(lastError) + alreadyRecorded = false ): unknown { - if (!isChunkFailure) { + if (!isKnownDynamicImportFailure(lastError)) { return lastError } const failureName = lastError instanceof Error ? lastError.name : 'unknown' const crumbKey = `${reloadKey}:${outcome}:${failureName}` if (!alreadyRecorded && !recordedExhaustionKeys.has(crumbKey)) { // error.name is library-controlled, so bound the set the way the breadcrumb - // and renderer-error key stores do rather than trusting its cardinality. - if (recordedExhaustionKeys.size >= MAX_RECORDED_EXHAUSTION_KEYS) { - recordedExhaustionKeys.clear() + // and renderer-error key stores do: evict oldest-first, never wholesale, so + // an overflow cannot re-open the whole set to a repeat burst. + while (recordedExhaustionKeys.size >= MAX_RECORDED_EXHAUSTION_KEYS) { + const oldestKey = recordedExhaustionKeys.values().next().value + if (oldestKey === undefined) { + break + } + recordedExhaustionKeys.delete(oldestKey) } recordedExhaustionKeys.add(crumbKey) recordReloadBreadcrumb('lazy_chunk_recovery_exhausted', reloadKey, failureMessage, outcome) @@ -271,7 +275,6 @@ export async function loadLazyWithRetry( // An unrelated failure must not clear a guard it did not set. throw lastError } - // Already classified just above; do not re-run the regex set. // Record the veto beside the resulting report before the ring can evict it. clearChunkReloadGuard() recordReloadBreadcrumb( @@ -280,14 +283,7 @@ export async function loadLazyWithRetry( failureMessage, 'guard-not-landed' ) - throw exhaustedRecoveryFailure( - lastError, - reloadKey, - failureMessage, - 'guard-not-landed', - true, - true - ) + throw exhaustedRecoveryFailure(lastError, reloadKey, failureMessage, 'guard-not-landed', true) } if (reloadGuardState === 'not-attempted') {