From 6d5a7711f36e534bc0870ebf175ce4ad7e814a07 Mon Sep 17 00:00:00 2001 From: vanceingalls Date: Sat, 11 Jul 2026 01:54:38 +0000 Subject: [PATCH] feat(producer): route eligible parallel screenshot/beginframe renders to streaming --- .../src/services/render/observability.ts | 7 ++++ .../src/services/renderOrchestrator.ts | 41 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/producer/src/services/render/observability.ts b/packages/producer/src/services/render/observability.ts index f793a47a4..ca542532e 100644 --- a/packages/producer/src/services/render/observability.ts +++ b/packages/producer/src/services/render/observability.ts @@ -68,6 +68,13 @@ export interface RenderCaptureObservability { deParallelRouter?: "routed" | "reverted"; /** Worker count the resolver would have used absent the router; undefined if it never fired. */ dePreRouterWorkers?: number; + /** + * Non-DE parallel-streaming router outcome (HF_CAPTURE_PARALLEL_STREAM): + * set when a multi-worker screenshot/BeginFrame render was routed through + * the interleaved streaming encoder instead of the parallel disk path. + * The value is the capture mode that streamed. Absent = not routed. + */ + captureParallelStream?: "screenshot" | "beginframe"; protocolTimeoutMs?: number; pageNavigationTimeoutMs?: number; playerReadyTimeoutMs?: number; diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index c5a39a82a..8328bfeba 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -1647,6 +1647,10 @@ export async function executeRenderJob( // render already executing in the same process. Threading this as a // local instead closes that cross-talk, not just the sequential leak. let deParallelStreamForced = false; + // Per-render (not process-global) signal that the NON-DE parallel-stream + // router fired — same threading discipline as deParallelStreamForced + // (see that flag's comment for why this must never be an env mutation). + let captureParallelStreamForced = false; let deSelfVerifyFallback = false; let deFallbackReason: string | undefined; let deDrainStats: import("./render/stages/captureStreamingStage.js").DeDrainStats | undefined; @@ -2238,6 +2242,39 @@ export async function executeRenderJob( deParallelRouter: deParallelRouter ?? "none", }); + // Non-DE parallel-streaming router — see shouldStreamParallelCapture. + // Mutually exclusive with the DE inversion/router above by construction + // (both DE predicates require useDrawElement; this requires its negation). + const captureParallelStreamEligible = shouldStreamParallelCapture({ + routerEnabled: process.env.HF_CAPTURE_PARALLEL_STREAM === "true", + workerCount, + useDrawElement: cfg.useDrawElement, + outputFormat, + streamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration), + layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions, + }); + if (captureParallelStreamEligible) { + captureParallelStreamForced = true; + // Which mode will stream: the engine picks beginframe only on Linux with + // headless-shell and no forced screenshot (frameCapture.ts preMode); + // everything else is screenshot. Recorded for telemetry cohorting. + const captureParallelStream = + process.platform === "linux" && !captureForceScreenshot ? "beginframe" : "screenshot"; + log.info( + `[Render] Parallel ${captureParallelStream} capture will stream to the encoder ` + + `(interleaved, ${workerCount} workers) instead of the disk path. ` + + "Set HF_CAPTURE_PARALLEL_STREAM=false to disable.", + ); + updateCaptureObservability({ captureParallelStream }); + // NOTE: no string data on the checkpoint — RenderObservationData string + // values are dropped unless the key is in observability.ts's + // ALLOWED_STRING_DATA_KEYS allow-list. The message carries the detail. + observability.checkpoint( + "worker_resolution", + `parallel ${captureParallelStream} capture routed to streaming`, + ); + } + if (workerCount > 1 && probeSession) { lastBrowserConsole = probeSession.browserConsoleBuffer; await closeCaptureSession(probeSession); @@ -2254,7 +2291,7 @@ export async function executeRenderJob( outputFormat, workerCount, job.duration, - deParallelStreamForced, + deParallelStreamForced || captureParallelStreamForced, ); log.info("streaming-encode gate", { enabled: useStreamingEncode, @@ -2498,7 +2535,7 @@ export async function executeRenderJob( workerCount, probeSession, outputFormat, - forceParallelStream: deParallelStreamForced, + forceParallelStream: deParallelStreamForced || captureParallelStreamForced, streamingEncoderOptions: { fps: job.config.fps, width,