From d4c32a937dc08bcedefccaf22e6e294b1aa2b0f2 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:57:59 -0700 Subject: [PATCH] perf(renderer): trim redundant work on the lazy-chunk failure path Hoist the dynamic-import message patterns to module scope so classification stops allocating seven RegExp objects per call, thread the already-computed classification into exhaustedRecoveryFailure so the guard-not-landed path does not re-run it, and bound recordedExhaustionKeys the way the breadcrumb and renderer-error key stores are bounded, since error.name is library-controlled. Failure path only; the success path is unchanged. --- src/renderer/src/lib/lazy-with-retry.ts | 43 ++++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/lib/lazy-with-retry.ts b/src/renderer/src/lib/lazy-with-retry.ts index 845ca7c5da4..f6c57cdeae4 100644 --- a/src/renderer/src/lib/lazy-with-retry.ts +++ b/src/renderer/src/lib/lazy-with-retry.ts @@ -103,6 +103,7 @@ let reloadRequestInFlight = false // One breadcrumb per reloadKey + outcome + error name (unkeyed call sites share // 'unknown'): a corrupt shared chunk can fail dozens of sibling imports at once, // which would otherwise flush the 30-entry breadcrumb ring. +const MAX_RECORDED_EXHAUSTION_KEYS = 128 const recordedExhaustionKeys = new Set() export function resetLazyChunkReloadRequestsForTest(): void { @@ -150,20 +151,38 @@ 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 + alreadyRecorded = false, + isChunkFailure = isKnownDynamicImportFailure(lastError) ): unknown { - if (!isKnownDynamicImportFailure(lastError)) { + if (!isChunkFailure) { 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() + } recordedExhaustionKeys.add(crumbKey) recordReloadBreadcrumb('lazy_chunk_recovery_exhausted', reloadKey, failureMessage, outcome) } return new LazyChunkLoadError(lastError, reloadKey) } +// Module scope: re-evaluating these literals would allocate fresh RegExp objects +// on every classification. +const DYNAMIC_IMPORT_FAILURE_PATTERNS = [ + /failed to fetch dynamically imported module/i, + /error loading dynamically imported module/i, + /importing a module script failed/i, + /failed to load module script/i, + /loading chunk .+ failed/i, + /unexpected token/i, + /unexpected end of (input|script|json)/i +] + function isKnownDynamicImportFailure(error: unknown): boolean { if (!(error instanceof Error)) { return false @@ -186,15 +205,7 @@ function isKnownDynamicImportFailure(error: unknown): boolean { return true } - return [ - /failed to fetch dynamically imported module/i, - /error loading dynamically imported module/i, - /importing a module script failed/i, - /failed to load module script/i, - /loading chunk .+ failed/i, - /unexpected token/i, - /unexpected end of (input|script|json)/i - ].some((pattern) => pattern.test(error.message)) + return DYNAMIC_IMPORT_FAILURE_PATTERNS.some((pattern) => pattern.test(error.message)) } export async function loadLazyWithRetry( @@ -260,6 +271,7 @@ 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( @@ -268,7 +280,14 @@ export async function loadLazyWithRetry( failureMessage, 'guard-not-landed' ) - throw exhaustedRecoveryFailure(lastError, reloadKey, failureMessage, 'guard-not-landed', true) + throw exhaustedRecoveryFailure( + lastError, + reloadKey, + failureMessage, + 'guard-not-landed', + true, + true + ) } if (reloadGuardState === 'not-attempted') {