From d77b03c24cd2e7c855c602fa8c4546f8076a1dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 14 Oct 2026 02:26:11 +0000 Subject: [PATCH] fix(producer): start segmented render workers together so slow media waits overlap (#5573) * fix(producer): start segmented render workers together so slow media waits overlap Each worker's page load used to wait for the previous worker's media, fonts and Tailwind to be ready, so a slow readiness wait was paid once per worker. Open every worker session at once, as non-segmented parallel capture already does. * fix(producer): trim the worker-opening comment * fix(producer): name the worker-opening helper instead of commenting it --- .../stages/captureSegmentedStage.test.ts | 30 +++++++++++++++++++ .../render/stages/captureSegmentedStage.ts | 14 ++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts b/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts index c4149bbca2..3245f16c7a 100644 --- a/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts +++ b/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts @@ -700,6 +700,36 @@ describe("runCaptureSegmentedStage", () => { expect(createdSessionConfigs).toEqual([expect.objectContaining({ enableBrowserPool: false })]); }); + it("opens every worker's session together, so readiness waits overlap", async () => { + let opening = 0; + let mostOpeningAtOnce = 0; + let id = 0; + const result = await runCaptureSegmentedStage({ + ...fakeStageInput({ totalFrames: 9 }), + probeSession: null, + segmentFrames: 3, + segmentDir: join(fixtureRoot, "concurrent-open"), + workerCount: 3, + sessionFactory: { + create: async () => { + mostOpeningAtOnce = Math.max(mostOpeningAtOnce, ++opening); + await new Promise((resolve) => setImmediate(resolve)); + opening--; + return fakeSession(id++); + }, + }, + deps: { + spawnEncoder: mock(async () => okEncoder()), + captureFrame: mock(async () => ({ buffer: Buffer.alloc(1) })), + concat: mock(async () => ({ success: true as const })), + closeSession: mock(async () => {}), + removeFile: () => {}, + }, + }); + expect(result.success).toBe(true); + expect(mostOpeningAtOnce).toBe(3); + }); + it("closes a fresh session whose initialization fails", async () => { closeCaptureSession.mockClear(); failInitializeSession = true; diff --git a/packages/producer/src/services/render/stages/captureSegmentedStage.ts b/packages/producer/src/services/render/stages/captureSegmentedStage.ts index 966b14c22d..5c9ad9c54e 100644 --- a/packages/producer/src/services/render/stages/captureSegmentedStage.ts +++ b/packages/producer/src/services/render/stages/captureSegmentedStage.ts @@ -408,6 +408,18 @@ async function createSegmentWorker(run: SegmentRun, id: number): Promise { + const opened = await Promise.allSettled( + Array.from({ length: run.workerCount }, (_, id) => createSegmentWorker(run, id)), + ); + for (const o of opened) if (o.status === "fulfilled") workersToCloseLater.push(o.value); + const failure = opened.find((o) => o.status === "rejected"); + if (failure && failure.status === "rejected") throw failure.reason; +} + /** Close this worker's browser and open a fresh one at a segment boundary. */ async function recycleWorkerSession( run: SegmentRun, @@ -585,7 +597,7 @@ export async function runCaptureSegmentedStage( try { assertNotAborted(); - for (let id = 0; id < run.workerCount; id++) workers.push(await createSegmentWorker(run, id)); + await openSegmentWorkersTogether(run, workers); // allSettled, not all: closing a session out from under a worker that is // still capturing orphans its ffmpeg and races the CDP connection, so // every worker has to stop before the finally runs. The first error is