Repository navigation
Conversation
There was a problem hiding this comment.
Blocking finding
[P1] The warmup neither targets the failing boundary nor preserves the compositor's monotonic-time contract — packages/engine/src/services/parallelCoordinator.ts:421-428
I reproduced #2477 from the base before evaluating this patch: at e038cc93c, executeWorkerTask initializes a fresh session and immediately enters captureFrameRange; the first operation for worker 1 is therefore the seek/capture for frame 36, with no paint-settlement barrier. That matches the reported transparent frames at 36/72/108.
This change does not warm that failing seek. discardWarmupCapture(session) uses its defaults, frame 0/time 0 (frameCapture.ts:3250-3262), even when the task starts at frame 36/time 1.2. My current-head negative proof asserted the helper call for runWorker(1, 36) and received only [session], not [session, 36, 1.2]. The new test at parallelCoordinator.test.ts:63-80 mocks the helper as a no-op and checks only call order, so it passes without exercising the frame/time contract.
More critically, blindly capturing a warmup frame reinstates a known compositor deadlock. The current production path documents why discardWarmupCapture is intentionally absent: a second beginFrame at an already-used frameTimeTicks wedges Chrome (packages/producer/src/services/distributed/renderChunk.ts:579-582). Commit 24f64f02c removed the same approach after real multi-chunk validation found both same-time and backward-time deadlocks. This PR calls the helper for worker 0 at frame 0/time 0, immediately followed by the real frame 0/time 0 capture, which is exactly the same-time failure mode. Passing each worker's startFrame would still duplicate that timestamp; passing startFrame - 1 was the already-reverted backward-time failure.
Please fix the actual first-seek paint race without issuing an extra screenshot/beginFrame at a duplicate or backward compositor timestamp—for example, a seek/paint-settlement primitive separated from capture. The regression must reproduce the real boundary: render/decode the transparent multi-worker fixture, assert non-transparent output at interior starts, and prove worker 0 completes without a protocol timeout. A mocked “warmup was called” assertion cannot establish either invariant.
What is good
The failure and abort tests at parallelCoordinator.test.ts:91-115 correctly preserve session cleanup if a pre-capture stage fails. That cleanup discipline should remain in the eventual fix.
CI snapshot: core build/lint/typecheck/test, Windows render/tests, producer suites, and CodeQL are green at 82803ad95598a9e690817f5f8162ac095178f2fa; several regression shards are still running. The blocker above is deterministic and independent of those pending lanes.
Verdict: REQUEST CHANGES
Reasoning: #2477 is reproducible in the base call graph, but this patch warms frame 0 rather than the worker boundary and reintroduces a previously validated Chrome compositor deadlock pattern. It needs a monotonic-time-safe paint-settlement fix and a real boundary-render regression test.
|
Two code changes that might address both review findings:
With the current defaults (frame 0 / time 0) the warmup seeks a frame the worker never renders, so the boundary race at
The deadlock documented in That would leave beginFrame mode still needing the seek/paint-settlement primitive the review asks for, but a mode-guarded, startFrame-targeted warmup could fix the screenshot path (where transparent overlay renders live) without reintroducing the reverted failure modes. WDYT? @mvanhorn |
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-review after taking over the fix at exact head 121a02e0ec9aa69c57e1fbf5e14759ef0ad8cf46.
The original unconditional warmup was unsafe because BeginFrame/drawElement capture cannot be repeated at the same compositor timestamp without risking the deadlock previously removed in 24f64f02c. The revised implementation confines paint settlement to the affected path: fresh non-zero screenshot workers only, at the exact first worker frame/time. Worker 0 and both compositor-driven capture modes are explicitly excluded, and the helper rejects non-screenshot sessions defensively. Cleanup on warmup failure/abort remains covered.
Evidence:
- TDD pinned exact frame/time, worker-0 exclusion, BeginFrame/drawElement exclusion, cleanup, and abort behavior; focused suite: 40/40.
- Full
@hyperframes/enginesuite with repo-compatible static FFmpeg: 44 files passed (1 skipped), 1008 tests passed (3 skipped). - Engine typecheck, build, oxfmt, oxlint, fallow, and commit hooks passed.
- Real four-worker transparent WebM regression render passed 60/60 frames; alpha-plane samples remained nonzero across every worker boundary.
- Fresh CI at this exact head is fully green, including Linux tests, both Windows render/test lanes, CodeQL, Preview parity, and all eight producer regression shards.
Verdict: APPROVE
Reasoning: The patch now fixes the screenshot-worker paint race at its actual boundary without reintroducing the duplicate-timestamp compositor deadlock, and tests plus complete current-head CI cover the relevant modes and multi-worker transparency path.
— Deepwork
Exact reporter failure was not reproduced; approval was premature. Reopening verification gate until RED→GREEN evidence exists.
miguel-heygen
left a comment
There was a problem hiding this comment.
Verification correction at head 121a02e0ec9aa69c57e1fbf5e14759ef0ad8cf46.
The revised patch is internally safe against the known duplicate-BeginFrame deadlock and passes synthetic multi-worker transparency coverage, but I did not reproduce the reporter's exact #2477 failure before the change. Therefore I cannot distinguish with sufficient evidence between (a) the first-paint race this patch addresses and (b) a second composition/environment-specific cause. I dismissed my approval; this remains a candidate fix pending a reporter artifact or equivalent deterministic RED→GREEN reproduction.
Verdict: COMMENT
Reasoning: Safety and regression evidence are strong, but root-cause closure is not proven without reproducing the reported blank-boundary failure and demonstrating that this change removes it.
— Deepwork
|
Got a deterministic RED→GREEN for this. In |
There was a problem hiding this comment.
Re-reviewed exact head 121a02e.
The revised scope is safe and matches the failure mechanism: only fresh non-zero screenshot workers take the settlement capture, using the same relative frame index and absolute composition time as their first real capture. Worker 0 and the BeginFrame/drawElement paths are excluded, so the duplicate/backward compositor-timestamp deadlock from the earlier revision is not reintroduced. The helper also restores capture counters, dedup state, and the last-frame buffer, including on failure.
Independent verification:
- focused coordinator/frame-capture tests: 40/40 passed
- full repository build passed
- four-worker transparent WebM rendered 60/60 frames
- direct VP9 alpha-plane inspection stayed nonzero at and around all three worker boundaries (15/30/45)
- exact-head CI is fully green, including Windows, CodeQL, preview parity, and all eight regression shards
I also ran the same four-worker fixture on the base commit. It was green there on this host, so I could not produce an environment-level RED-to-GREEN; the original report remains load-sensitive and no reporter artifact was supplied. That limits certainty about exact issue closure, but it does not expose a code blocker: the deterministic coordinator contract covers the missing first-worker settlement, and the implementation is narrowly isolated from the known compositor-clock hazard.
One non-blocking test note: the checked-in regression fixture allows two visual failures, so the direct boundary-alpha assertion remains manual rather than encoded. Tightening that would improve future proof, but the unit contract and current-head empirical result are sufficient for this patch.
Verdict: APPROVE
Reasoning: The current implementation safely closes the missing non-zero screenshot-worker settlement path, preserves capture state, avoids the known duplicate-timestamp modes, and has proportional unit, render, alpha-plane, and full-CI evidence.
— Magi
miguel-heygen
left a comment
There was a problem hiding this comment.
Correction to my prior approval after exact-head verification.
Blocking finding
The warmup does not fully restore capture performance state. capturePerf.frameMs is a mutable array, but discardWarmupCapture snapshots capturePerf with a shallow spread. captureFrameCore appends the discarded sample to that same array; assigning the shallow snapshot back therefore retains the warmup sample and biases getCapturePerfSummary().p50TotalMs.
The implementation comment that all capturePerf fields are primitive is false: the type includes frameMs: number[]. The existing fake session omits frameMs under an unknown cast, so the tests never exercise the alias.
I added the minimal missing test locally at exact head: seed frameMs with [13, 17], clone the expected array, push 50 in the injected warmup capture, then assert full restoration. Current head fails deterministically with received [13, 17, 50] (7 tests pass, 1 fails).
Please snapshot frameMs by value (and restore it on both success and failure) and add the focused regression before merge.
Issue-closure correction
The distributed single-worker path explicitly does not call discardWarmupCapture because of the BeginFrame timestamp contract. The coordinator test proves the newly selected call ordering, not a stale/transparent browser frame, and all real base/head comparisons remained identical. The patch may be useful defensive hardening, but exact #2477 closure is not demonstrated without the reporter artifact/environment. Change Fixes #2477 to Related to #2477 and keep the issue open pending reporter validation.
Verdict: REQUEST CHANGES
Reasoning: Current head leaks the discarded warmup timing sample into p50 telemetry through a shallow-copied frameMs array, and the existing tests omit the nested state. The fix is small and deterministic; exact issue closure should remain provisional.
— Magi
jrusso1020
left a comment
There was a problem hiding this comment.
Additive review after Magi's accounting finding.
The mode/start guard at packages/engine/src/services/parallelCoordinator.ts:476 is a good correction: it keeps worker 0 and compositor-clock-driven capture modes out of the duplicate-timestamp path.
Blocking finding
[blocker] The warmup settles one seek, then the real capture immediately issues another seek before reading pixels — packages/engine/src/services/parallelCoordinator.ts:422,365-368,481; packages/engine/src/services/frameCapture.ts:2145-2151
discardWarmupCapture is a full captureFrameCore: it seeks to the boundary and screenshots it. captureFrameRange then calls the normal capture for that same boundary, and captureFrameCore seeks to the same timestamp again before taking the emitted screenshot. For a supported composition or adapter that schedules rAF/compositor work on every seek, the throwaway screenshot can settle the first seek, while the second seek schedules fresh work and recreates the original stale/transparent-frame race.
The engine already documents the required settlement shape at packages/engine/src/services/frameCapture.ts:3492-3500: seek once, take a throwaway screenshot to force rAF work, then take the retained screenshot without seeking again. The coordinator test at packages/engine/src/services/parallelCoordinator.test.ts:56-82 mocks warmup and capture separately, so it cannot exercise this invariant; the checked-in visual fixture also permits two failed frames (packages/producer/tests/webm-transparency/meta.json:6).
Please make the first-frame path prepare/seek once and capture twice, or introduce a screenshot-only paint barrier that does not run prepareFrameForCapture again. Add coverage that pins the absence of a second seek and, ideally, a zero-alpha boundary assertion.
I also agree with Magi's existing blocker that the shallow capturePerf snapshot leaks the discarded frameMs sample into p50 accounting. That needs the focused value-copy regression already described there.
Verdict: REQUEST CHANGES
Reasoning: The current implementation does not guarantee that the screenshot it emits observes the paint forced by the warmup, because it performs another seek in between. Together with the definite frameMs state leak, current head should not merge.
— Codex
discardWarmupCapture no longer runs a full captureFrameCore: the warmup settles the seek once and the real capture reads pixels without issuing a second seek at the same boundary. Test expectations updated.
|
Pushed 04b68b2 for the blocker: the warmup now settles the boundary seek once and the real capture reads pixels without re-seeking, so the duplicate-timestamp path is gone. Engine suite is green locally (1008 passed / 44 files) plus typecheck and lint. |
|
We hit the distributed variant of this bug and can add data that may help land this PR. Symptom (measured): rendering a composition with a Why the existing warmup can't be used there: A BeginFrame-safe settle that worked for us: issue one discarded Verified end-to-end on our fork (base v0.8.9): the same 6-chunk render's boundary frames went from Δ 8.6/7.4/9.8/6.2 to 1.7/1.1/2.2/1.1 — indistinguishable from a single-chunk reference. Engine suite green with 6 added tests (tick-slot bounds, refuse-after-first-capture, per-mode routing). Happy to open a PR with the beginframe branch on top of this one if useful. |
perfBefore shared the frameMs array by reference, so a mutation inside the throwing capture also mutated the snapshot and the restoration assertion could never fail. Deep-copy frameMs and push a sentinel in the failing capture so the test actually pins frameMs restoration.
|
The blocking finding is addressed at the current head ( What changed: The mode and start guard moved with it: I also just pushed a small test fix. The throw-path assertion in @joekjoshua thank you for the distributed measurements, they are useful. The per-chunk symptom is consistent with the same root cause: every chunk with |
…age group bun run --filter cannot topologically order the workspace because of the producer<->aws-lambda and core<->parsers dependency cycles, so aws-lambda and gcp-cloud-run raced the concurrent producer/core rebuild inside the same filter group. On low-core machines the adapters' tsc reads producer's dist mid-rewrite and fails with a spurious TS2322. Building the adapters in their own group after producer finishes makes bun run build deterministic.
…captureFrameCore The parallelCoordinator already requests paint settlement only for fresh non-zero screenshot workers, but captureFrameCore itself would have issued the throwaway Page.captureScreenshot in any capture mode if the flag ever leaked. Interleaving an extra screenshot with BeginFrame/drawElement capture is the duplicate-compositor-tick hazard class discardWarmupCapture guards against, so the mode check now lives at the barrier itself. Added a regression test pinning that a BeginFrame session ignores settlePaint.
…oordinator complexity The settle-paint edits shifted lines in executeWorkerTask, which made fallow re-flag the coordinator's inherited complexity as new. Extract the repeated HF_DE_PAR_DEBUG log guards into logParDebug (a real two-branch reduction) and mark the remaining session-lifecycle branching with the same fallow-ignore-next-line convention captureFrameRange already uses.
|
Thanks both — the head now addresses each blocking finding, with fresh commits on top of the reviewed revision. @miguel-heygen — shallow @jrusso1020 — warmup settle wasted by a second seek before the pixel read: the coordinator no longer runs a separate warmup capture at all. Instead, the first real capture of a fresh non-zero screenshot worker passes a Also included: a small root build-script fix ( Focused suites: 41/41. |
|
Closing — this PR has merge conflicts and has been inactive. Thank you for the contribution! Please feel free to reopen with a rebased branch if you'd like to continue. |
What
Settles the first seek of each fresh non-zero screenshot worker in parallel capture, so no stale/transparent frame is emitted at a worker chunk boundary.
Why
A non-zero parallel worker's first pixel read can race the paint scheduled by its first
__hf.seek(), so the first frame of a worker chunk could come back stale or fully transparent. Issue #2477 reported a transparent frame at each chunk boundary.How
captureFrameCoreaccepts asettlePaintflag: after the singleprepareFrameForCaptureseek, it takes one throwawayPage.captureScreenshotso the scheduled rAF/compositor work lands, then takes the retained screenshot — no second seek, so the emitted pixels observe the settled paint.captureFrameRangerequests settlement only for the first frame of a fresh non-zero screenshot worker. Worker 0 and BeginFrame/drawElement sessions are excluded — enforced both at the coordinator and insidecaptureFrameCoreitself — so no duplicate or backward compositor timestamps are introduced (the deadlock class removed in24f64f02cstays out).discardWarmupCapture(no longer on the coordinator path, still an exported helper) now snapshotscapturePerfwith a by-value copy of the mutableframeMsarray and restores it in afinally, so a discarded warmup sample can no longer leak intogetCapturePerfSummary().p50TotalMson either the success or the failure path. The stale "all fields are primitive" comment is gone.The root
bun run buildwas also made deterministic (the aws-lambda / gcp-cloud-run builds raced producer's concurrent rebuild because workspace dependency cycles disable bun's topological ordering) — surfaced while validating this change on a low-core machine.Test plan
frameCapture-discardWarmup.test.ts: pins the settlement shape (exactly one seek, throwaway + retained screenshot, retained buffer emitted), pins that a BeginFrame session ignoressettlePaint, and pins fullcapturePerfrestoration — includingframeMsby value — on both the success and throw paths.parallelCoordinator.test.ts: settle flag on the first frame of non-zero screenshot workers only, worker-0 and BeginFrame/drawElement exclusions, frame accounting, failure and abort handling, session cleanup. Focused suites: 41 passed.oxfmt,oxlint,fallow, and engine typecheck are clean.Related to #2477 — kept open pending reporter validation; exact closure is not demonstrated without the reporter's artifact/environment.