From 81c275a77a1a936642ad0e8e900d1d6801820e1c Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Mon, 4 May 2026 10:55:33 +0200 Subject: [PATCH 1/5] test(cloudflare): Reduce flakiness for cloudflare with sub workers --- .../cloudflare-integration-tests/runner.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 542ffe82b802..ac2baf648680 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -41,6 +41,9 @@ function deferredPromise( }; } +/** Extra wait after wrangler's DEV_SERVER_READY for multi-worker dev (main + sub-worker). */ +const DEV_SERVER_READY_SETTLE_MS = 100; + type Expected = Envelope | ((envelope: Envelope) => void); type StartResult = { @@ -75,6 +78,8 @@ export function createRunner(...paths: string[]) { const ignored: Set = new Set(['session', 'sessions', 'client_report']); let serverUrl: string | undefined; const extraWranglerArgs: string[] = []; + /** True when this scenario runs a second wrangler process (wrangler-sub-worker.jsonc). */ + const hasSubWorkerDevServer = existsSync(join(testPath, 'wrangler-sub-worker.jsonc')); return { withServerUrl: function (url: string) { @@ -226,7 +231,7 @@ export function createRunner(...paths: string[]) { } } - if (existsSync(join(testPath, 'wrangler-sub-worker.jsonc'))) { + if (hasSubWorkerDevServer) { childSubWorker = spawn( 'wrangler', [ @@ -283,7 +288,17 @@ export function createRunner(...paths: string[]) { childSubWorker?.on('error', onChildError); child.on('error', onChildError); - child.on('message', (msg: string) => onChildMessage(msg, setWorkerPort)); + child.on('message', (msg: string) => + onChildMessage(msg, port => { + // Miniflare can accept DEV_SERVER_READY before service bindings to the sub-worker are usable. + // A fixed settle time is simpler than HTTP probes; only applied for two-process scenarios. + if (hasSubWorkerDevServer) { + setTimeout(() => setWorkerPort(port), DEV_SERVER_READY_SETTLE_MS); + } else { + setWorkerPort(port); + } + }), + ); }) .catch(e => reject(e)); From f96420ba39484118a6b914c7011e45d23c74e26c Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 4 May 2026 12:46:07 +0200 Subject: [PATCH 2/5] Revert "test(cloudflare): Reduce flakiness for cloudflare with sub workers" This reverts commit 81c275a77a1a936642ad0e8e900d1d6801820e1c. --- .../cloudflare-integration-tests/runner.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index ac2baf648680..542ffe82b802 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -41,9 +41,6 @@ function deferredPromise( }; } -/** Extra wait after wrangler's DEV_SERVER_READY for multi-worker dev (main + sub-worker). */ -const DEV_SERVER_READY_SETTLE_MS = 100; - type Expected = Envelope | ((envelope: Envelope) => void); type StartResult = { @@ -78,8 +75,6 @@ export function createRunner(...paths: string[]) { const ignored: Set = new Set(['session', 'sessions', 'client_report']); let serverUrl: string | undefined; const extraWranglerArgs: string[] = []; - /** True when this scenario runs a second wrangler process (wrangler-sub-worker.jsonc). */ - const hasSubWorkerDevServer = existsSync(join(testPath, 'wrangler-sub-worker.jsonc')); return { withServerUrl: function (url: string) { @@ -231,7 +226,7 @@ export function createRunner(...paths: string[]) { } } - if (hasSubWorkerDevServer) { + if (existsSync(join(testPath, 'wrangler-sub-worker.jsonc'))) { childSubWorker = spawn( 'wrangler', [ @@ -288,17 +283,7 @@ export function createRunner(...paths: string[]) { childSubWorker?.on('error', onChildError); child.on('error', onChildError); - child.on('message', (msg: string) => - onChildMessage(msg, port => { - // Miniflare can accept DEV_SERVER_READY before service bindings to the sub-worker are usable. - // A fixed settle time is simpler than HTTP probes; only applied for two-process scenarios. - if (hasSubWorkerDevServer) { - setTimeout(() => setWorkerPort(port), DEV_SERVER_READY_SETTLE_MS); - } else { - setWorkerPort(port); - } - }), - ); + child.on('message', (msg: string) => onChildMessage(msg, setWorkerPort)); }) .catch(e => reject(e)); From 70535eb39b392877391a74fcbc45b87ca2d7270b Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 4 May 2026 12:54:35 +0200 Subject: [PATCH 3/5] test(cloudflare): Wait for the port to be ready --- .../cloudflare-integration-tests/runner.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 542ffe82b802..c07e7feca39d 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -116,6 +116,7 @@ export function createRunner(...paths: string[]) { let envelopeCount = 0; const envelopeWaiters: { expected: Expected; resolve: () => void; reject: (e: unknown) => void }[] = []; const { resolve: setWorkerPort, promise: workerPortPromise } = deferredPromise(); + const { resolve: setSubWorkerPort, promise: subWorkerPortPromise, reject: rejectSubWorker } = deferredPromise(); let child: ReturnType | undefined; let childSubWorker: ReturnType | undefined; @@ -218,11 +219,11 @@ export function createRunner(...paths: string[]) { reject(e); }; - function onChildMessage(message: string, onReady?: (port: number) => void): void { + function onChildMessage(message: string, onReady: (port: number) => void): void { const msg = JSON.parse(message) as { event: string; port?: number }; if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') { if (process.env.DEBUG) log('worker ready on port', msg.port); - onReady?.(msg.port); + onReady(msg.port); } } @@ -245,14 +246,14 @@ export function createRunner(...paths: string[]) { { stdio, signal }, ); - // Wait for the sub-worker to be ready before starting the main worker - await new Promise((resolveSubWorker, rejectSubWorker) => { - childSubWorker!.on('message', (msg: string) => onChildMessage(msg, () => resolveSubWorker())); - childSubWorker!.on('error', rejectSubWorker); - childSubWorker!.on('exit', code => { - rejectSubWorker(new Error(`Sub-worker exited with code ${code}`)); - }); + childSubWorker.on('message', (msg: string) => onChildMessage(msg, setSubWorkerPort)); + childSubWorker.on('error', rejectSubWorker); + childSubWorker.on('exit', code => { + rejectSubWorker(new Error(`Sub-worker exited with code ${code}`)); }); + + // Wait for the sub-worker to be ready before starting the main worker + await subWorkerPortPromise; } child = spawn( From 952baff7190464b74d0190336db87f2591e2ae5a Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 4 May 2026 13:34:21 +0200 Subject: [PATCH 4/5] fixup! test(cloudflare): Wait for the port to be ready --- .../cloudflare-integration-tests/runner.ts | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index c07e7feca39d..778d54d596b1 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -116,7 +116,6 @@ export function createRunner(...paths: string[]) { let envelopeCount = 0; const envelopeWaiters: { expected: Expected; resolve: () => void; reject: (e: unknown) => void }[] = []; const { resolve: setWorkerPort, promise: workerPortPromise } = deferredPromise(); - const { resolve: setSubWorkerPort, promise: subWorkerPortPromise, reject: rejectSubWorker } = deferredPromise(); let child: ReturnType | undefined; let childSubWorker: ReturnType | undefined; @@ -209,22 +208,33 @@ export function createRunner(...paths: string[]) { if (process.env.DEBUG) log('Starting scenario', testPath); - const stdio: ('inherit' | 'ipc' | 'ignore')[] = process.env.DEBUG - ? ['inherit', 'inherit', 'inherit', 'ipc'] - : ['ignore', 'ignore', 'ignore', 'ipc']; - const onChildError = (e: Error) => { // eslint-disable-next-line no-console console.error('Error starting child process:', e); reject(e); }; - function onChildMessage(message: string, onReady: (port: number) => void): void { - const msg = JSON.parse(message) as { event: string; port?: number }; - if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') { - if (process.env.DEBUG) log('worker ready on port', msg.port); - onReady(msg.port); - } + // Inspired by workers-sdk: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/e2e/helpers/wrangler.ts + function waitForReady(childProcess: ReturnType): Promise { + return new Promise((resolve, reject) => { + const stdout = childProcess.stdout; + if (!stdout) { + reject(new Error('No stdout available')); + return; + } + + let output = ''; + stdout.on('data', (chunk: Buffer) => { + const text = chunk.toString(); + if (process.env.DEBUG) process.stdout.write(text); + output += text; + + const match = output.match(/Ready on (https?:\/\/[^\s]+)/); + if (match?.[1]) { + resolve(parseInt(new URL(match[1]).port, 10)); + } + }); + }); } if (existsSync(join(testPath, 'wrangler-sub-worker.jsonc'))) { @@ -243,17 +253,15 @@ export function createRunner(...paths: string[]) { '--inspector-port', '0', ], - { stdio, signal }, + { stdio: ['ignore', 'pipe', 'inherit'], signal }, ); - childSubWorker.on('message', (msg: string) => onChildMessage(msg, setSubWorkerPort)); - childSubWorker.on('error', rejectSubWorker); + childSubWorker.on('error', onChildError); childSubWorker.on('exit', code => { - rejectSubWorker(new Error(`Sub-worker exited with code ${code}`)); + onChildError(new Error(`Sub-worker exited with code ${code}`)); }); - // Wait for the sub-worker to be ready before starting the main worker - await subWorkerPortPromise; + await waitForReady(childSubWorker); } child = spawn( @@ -274,7 +282,7 @@ export function createRunner(...paths: string[]) { '0', ...extraWranglerArgs, ], - { stdio, signal }, + { stdio: ['ignore', 'pipe', 'inherit'], signal }, ); CLEANUP_STEPS.add(() => { @@ -284,7 +292,7 @@ export function createRunner(...paths: string[]) { childSubWorker?.on('error', onChildError); child.on('error', onChildError); - child.on('message', (msg: string) => onChildMessage(msg, setWorkerPort)); + waitForReady(child).then(setWorkerPort).catch(reject); }) .catch(e => reject(e)); From 87dbe466b4ebc07c3a5c51c4b3702b96909ee1da Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 4 May 2026 13:43:31 +0200 Subject: [PATCH 5/5] fixup! fixup! test(cloudflare): Wait for the port to be ready --- dev-packages/cloudflare-integration-tests/runner.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 778d54d596b1..0d19e2e4384b 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -292,7 +292,10 @@ export function createRunner(...paths: string[]) { childSubWorker?.on('error', onChildError); child.on('error', onChildError); - waitForReady(child).then(setWorkerPort).catch(reject); + + const workerPort = await waitForReady(child); + + setWorkerPort(workerPort); }) .catch(e => reject(e));