@@ -54,6 +54,7 @@ import type {
5454 CaptureWarning ,
5555 SubTimelineWaitOutcome ,
5656} from "../types.js" ;
57+ export { isMemoryExhaustionError , isTransientBrowserError } from "./captureFailure.js" ;
5758
5859export type { CaptureOptions , CaptureResult , CaptureBufferResult , CapturePerfSummary } ;
5960
@@ -3431,98 +3432,3 @@ export function getCapturePerfSummary(session: CaptureSession): CapturePerfSumma
34313432 deNcprFallbacks : session . deNcprFallbacks ?? 0 ,
34323433 } ;
34333434}
3434-
3435- // ── Transient browser error classification ─────────────────────────────────
3436- // Puppeteer/Chrome can fail with transient errors that succeed on retry with a
3437- // fresh browser session. These are infrastructure-level failures (frame
3438- // detachment, connection drop, OOM kill, launch failure) — NOT composition bugs.
3439-
3440- const TRANSIENT_BROWSER_ERROR_PATTERNS = [
3441- / N a v i g a t i n g f r a m e w a s d e t a c h e d / i,
3442- / T a r g e t c l o s e d / i,
3443- / S e s s i o n c l o s e d / i,
3444- / b r o w s e r h a s d i s c o n n e c t e d / i,
3445- / P a g e c r a s h e d / i,
3446- / E x e c u t i o n c o n t e x t w a s d e s t r o y e d / i,
3447- / C a n n o t f i n d c o n t e x t w i t h s p e c i f i e d i d / i,
3448- / F a i l e d t o l a u n c h t h e b r o w s e r p r o c e s s / i,
3449- / N a v i g a t i o n t i m e o u t o f \d + m s e x c e e d e d / i,
3450- / E C O N N R E F U S E D / i,
3451- // pollHfReady's own timeout — thrown when window.__renderReady never flips
3452- // true within playerReadyTimeout. "Runtime ready: false" means init simply
3453- // didn't finish in time (commonly a slow/contended host, e.g. several
3454- // concurrent renders), which a fresh session usually clears on retry. This
3455- // is distinct from the "Runtime ready: true" fast-fail case a few lines up
3456- // in pollHfReady (no timeline + no data-duration) — that's a genuine
3457- // authoring bug and intentionally NOT matched here, so it still fails fast.
3458- / C o m p o s i t i o n h a s z e r o d u r a t i o n [ \s \S ] * R u n t i m e r e a d y : f a l s e / ,
3459- ] ;
3460-
3461- export function isTransientBrowserError ( error : unknown ) : boolean {
3462- const message = error instanceof Error ? error . message : String ( error ) ;
3463- return TRANSIENT_BROWSER_ERROR_PATTERNS . some ( ( pattern ) => pattern . test ( message ) ) ;
3464- }
3465-
3466- // ── Memory-exhaustion classification ────────────────────────────────────────
3467- // A render can run the Node process (or a page-side allocation) out of memory
3468- // on an oversized composition — huge canvas, thousands of frames, or a very
3469- // large frame cache. These surface as cryptic V8 RangeErrors ("Set maximum
3470- // size exceeded", "Invalid array length"/"string length", "Array buffer
3471- // allocation failed") or a hard V8 heap-limit abort. They are NOT transient
3472- // (a retry re-hits the same ceiling) and NOT composition-logic bugs — they're
3473- // resource limits. Classify them so the caller can surface actionable guidance
3474- // (lower resolution / fps / duration, or enable low-memory mode) instead of a
3475- // raw RangeError.
3476-
3477- // Deliberately specific: each pattern is a distinct V8/Node allocation-failure
3478- // signature. We intentionally do NOT match a bare /out of memory/ — that
3479- // substring appears in benign browser-console noise (WebGL `CONTEXT_LOST … out
3480- // of memory`, GPU driver notes) that gets carried into the error path, and
3481- // misclassifying it would replace the real failure message with generic OOM
3482- // guidance.
3483- const MEMORY_EXHAUSTION_ERROR_PATTERNS = [
3484- / S e t m a x i m u m s i z e e x c e e d e d / i,
3485- / M a p m a x i m u m s i z e e x c e e d e d / i,
3486- / I n v a l i d (?: a r r a y | s t r i n g ) l e n g t h / i,
3487- / A r r a y b u f f e r a l l o c a t i o n f a i l e d / i,
3488- / C a n n o t c r e a t e a s t r i n g l o n g e r t h a n / i,
3489- / R e a c h e d h e a p l i m i t / i,
3490- / J a v a S c r i p t h e a p o u t o f m e m o r y / i,
3491- ] ;
3492-
3493- // The producer's deployed runtime is Bun (JavaScriptCore), not Node (V8) —
3494- // see `packages/gcp-cloud-run/Dockerfile`'s `CMD ["bun", "dist/server.js"]`.
3495- // JSC's own allocation-failure message for the equivalent single-oversized-
3496- // allocation RangeErrors above is the bare string "Out of memory" (verified:
3497- // `new Uint8Array(Number.MAX_SAFE_INTEGER)`, an unbounded `Set`, and
3498- // `"x".repeat(2**53)` all throw exactly this under Bun) — none of the V8
3499- // patterns above match it. This is exactly the substring the comment above
3500- // says NOT to match anywhere in the message (benign browser-console noise
3501- // like a WebGL `CONTEXT_LOST … out of memory` carries that phrase too), so
3502- // this checks the ENTIRE (trimmed) message equals it, not merely contains
3503- // it — a compound message with other text around the phrase still misses.
3504- const BUN_MEMORY_EXHAUSTION_EXACT_MESSAGE = / ^ o u t o f m e m o r y \. ? $ / i;
3505-
3506- // The parallel-DE capture path — the exact cohort the OOM-aware retry in
3507- // renderOrchestrator.ts targets — never reaches isMemoryExhaustionError with
3508- // a bare message: `executeParallelCapture`/`formatWorkerFailure`
3509- // (parallelCoordinator.ts) always wrap a worker's error as
3510- // "Worker N: <message>", optionally suffixed "; diagnostics: ..." and joined
3511- // with other failed workers' segments via "; ", all prefixed
3512- // "[Parallel] Capture failed: ". The exact-match check above is defeated by
3513- // that wrapping entirely (verified) — this pattern recovers the Bun OOM
3514- // signal by requiring "out of memory" appear immediately after "Worker N: "
3515- // and immediately before end-of-string, ";", or ".", i.e. as the WHOLE
3516- // worker-segment content, not merely somewhere inside it. This preserves the
3517- // exact-match property (no bare "out of memory" substring inside otherwise-
3518- // unrelated worker text, e.g. "Worker 2: WebGL context lost, out of memory
3519- // reported by driver" does NOT match) while surviving this codebase's own
3520- // error-flattening.
3521- const BUN_MEMORY_EXHAUSTION_WRAPPED_WORKER_MESSAGE = / \b w o r k e r \d + : o u t o f m e m o r y \. ? (?: ; | $ ) / i;
3522-
3523- export function isMemoryExhaustionError ( error : unknown ) : boolean {
3524- const message = error instanceof Error ? error . message : String ( error ) ;
3525- if ( BUN_MEMORY_EXHAUSTION_EXACT_MESSAGE . test ( message . trim ( ) ) ) return true ;
3526- if ( BUN_MEMORY_EXHAUSTION_WRAPPED_WORKER_MESSAGE . test ( message ) ) return true ;
3527- return MEMORY_EXHAUSTION_ERROR_PATTERNS . some ( ( pattern ) => pattern . test ( message ) ) ;
3528- }
0 commit comments