Skip to content

Commit ec791e9

Browse files
committed
fix(producer): screenshot-retry recovery for disk-path drawElement verify failures
1 parent 060b6f8 commit ec791e9

2 files changed

Lines changed: 96 additions & 25 deletions

File tree

packages/producer/src/services/render/capturePlan.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ function revertedRouting(routing: CaptureRouting): CaptureRouting {
124124

125125
/** Pure, exhaustive capture fallback transition. The input plan is never mutated. */
126126
export function replanAfterFailure(plan: CapturePlan, failure: CapturePlanFailure): CapturePlan {
127+
// Disk-path drawElement self-verification (parallel disk workers under the
128+
// explicit fast-capture opt-in) can also trip — the retry stays on the disk
129+
// path but forces the screenshot baseline.
130+
if (plan.kind === "sdr_disk" && failure.kind === "draw_element_verification") {
131+
return createCapturePlan({
132+
...plan,
133+
forceScreenshot: true,
134+
useStreamingEncode: false,
135+
useLayeredComposite: false,
136+
forceParallelStream: false,
137+
});
138+
}
127139
if (plan.kind !== "sdr_streaming") {
128140
throw new Error(`Cannot apply ${failure.kind} to ${plan.kind} capture plan`);
129141
}

packages/producer/src/services/renderOrchestrator.ts

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
createCapturePlan,
110110
replanAfterFailure,
111111
type CapturePlan,
112+
type SdrDiskCapturePlan,
112113
type CaptureRouting,
113114
} from "./render/capturePlan.js";
114115
import { normalizeErrorMessage } from "../utils/errorMessage.js";
@@ -3144,34 +3145,92 @@ async function executeRenderPipeline(input: {
31443145
if (capturePlan.kind !== "sdr_disk") {
31453146
throw new Error(`Disk capture requires sdr_disk plan; got ${capturePlan.kind}`);
31463147
}
3147-
const diskPlan = capturePlan;
31483148
// ── Disk-based capture (original flow) ────────────────────────────
31493149
resetCaptureAttemptProgress(job);
31503150
const captureFrameStart = Date.now();
3151-
const captureRes = await observeRenderStage(
3152-
observability,
3153-
"capture_disk",
3154-
captureStageObservationData({ needsAlpha: diskPlan.needsAlpha }),
3155-
() =>
3156-
runCaptureStage({
3157-
fileServer: activeFileServer,
3158-
workDir,
3159-
framesDir,
3160-
job,
3161-
totalFrames,
3162-
cfg,
3163-
plan: diskPlan,
3164-
log,
3165-
probeSession,
3166-
captureAttempts,
3167-
dedupPerfs,
3168-
buildCaptureOptions,
3169-
createRenderVideoFrameInjector,
3170-
abortSignal: executionSignal,
3171-
assertNotAborted,
3172-
onProgress,
3173-
}),
3174-
);
3151+
const invokeDiskCapture = (diskPlan: SdrDiskCapturePlan) =>
3152+
observeRenderStage(
3153+
observability,
3154+
"capture_disk",
3155+
captureStageObservationData({ needsAlpha: diskPlan.needsAlpha }),
3156+
() =>
3157+
runCaptureStage({
3158+
fileServer: activeFileServer,
3159+
workDir,
3160+
framesDir,
3161+
job,
3162+
totalFrames,
3163+
cfg,
3164+
plan: diskPlan,
3165+
log,
3166+
probeSession,
3167+
captureAttempts,
3168+
dedupPerfs,
3169+
buildCaptureOptions,
3170+
createRenderVideoFrameInjector,
3171+
abortSignal: executionSignal,
3172+
assertNotAborted,
3173+
onProgress,
3174+
}),
3175+
);
3176+
let captureRes;
3177+
try {
3178+
captureRes = await invokeDiskCapture(capturePlan);
3179+
} catch (err) {
3180+
// Disk-path drawElement self-verification tripped (a parallel disk
3181+
// worker's sampled frame diverged from its pre-injection ground
3182+
// truth — reachable only under the explicit fast-capture opt-in).
3183+
// Same recovery contract as the streaming drain: re-render on the
3184+
// screenshot baseline. Anything else keeps its existing semantics.
3185+
if (
3186+
!isDrawElementVerificationError(err) ||
3187+
err instanceof RenderCancelledError ||
3188+
executionSignal?.aborted === true
3189+
) {
3190+
throw err;
3191+
}
3192+
const verifyDetails = getDrawElementVerificationDetails(err);
3193+
deSelfVerifyFallback = true;
3194+
deFallbackReason = verifyDetails?.kind ?? "psnr";
3195+
deFallbackFailedDb = roundDb(verifyDetails?.failedDb);
3196+
deFallbackFrameIndex = verifyDetails?.frameIndex;
3197+
deFallbackThresholdDb = roundDb(verifyDetails?.verifyThresholdDb);
3198+
log.warn(
3199+
"[Render] drawElement self-verification failed on the parallel disk path; " +
3200+
"re-rendering via screenshot",
3201+
{ error: err instanceof Error ? err.message : String(err) },
3202+
);
3203+
observability.checkpoint(
3204+
"capture_disk",
3205+
"drawElement self-verify failed; retrying with forceScreenshot",
3206+
);
3207+
// The failed attempt's frames are untrusted BUT satisfy the
3208+
// completeness check — wipe them so the retry re-captures everything
3209+
// instead of silently keeping damaged files.
3210+
rmSync(framesDir, { recursive: true, force: true });
3211+
mkdirSync(framesDir, { recursive: true });
3212+
resetCaptureAttemptProgress(job);
3213+
dedupPerfs.length = 0;
3214+
cfg.useDrawElement = false;
3215+
probeSession = null;
3216+
capturePlan = replanAfterFailure(capturePlan, { kind: "draw_element_verification" });
3217+
syncCapturePlan();
3218+
updateCaptureObservability({
3219+
forceScreenshot: capturePlan.forceScreenshot,
3220+
deSelfVerifyFallback,
3221+
deFallbackReason,
3222+
deFallbackFailedDb,
3223+
deFallbackFrameIndex,
3224+
deFallbackThresholdDb,
3225+
});
3226+
if (capturePlan.kind !== "sdr_disk") {
3227+
throw new Error(`Disk verify retry requires sdr_disk plan; got ${capturePlan.kind}`);
3228+
}
3229+
captureRes = await invokeDiskCapture(capturePlan);
3230+
// The first attempt's error marked the phase failed; the retry
3231+
// recovered it — don't brand the render as failed in telemetry.
3232+
observability.clearFailure("capture_disk");
3233+
}
31753234
const captureFrameMs = Date.now() - captureFrameStart;
31763235
workerCount = captureRes.workerCount;
31773236
updateCaptureObservability({ workerCount });

0 commit comments

Comments
 (0)