diff --git a/.changeset/fast-frame-refill.md b/.changeset/fast-frame-refill.md deleted file mode 100644 index 895f5a6922..0000000000 --- a/.changeset/fast-frame-refill.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@workflow/world-vercel': patch ---- - -Avoid repeatedly copying partial event-frame bodies as network chunks arrive. diff --git a/.changeset/revert-fast-frame-refill.md b/.changeset/revert-fast-frame-refill.md new file mode 100644 index 0000000000..004689c51a --- /dev/null +++ b/.changeset/revert-fast-frame-refill.md @@ -0,0 +1,5 @@ +--- +'@workflow/world-vercel': patch +--- + +Revert the deferred frame-buffer concatenation in `decodeFrames`: stashing yielded chunk views across `await`s let the stream reuse their backing buffers before the copy, corrupting encrypted frame payloads (vercel-world e2e and WS transport lanes failed fleet-wide). Chunks are copied on receipt again. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9e702af56..1fe29227e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -320,7 +320,10 @@ jobs: e2e-vercel-prod: name: E2E Vercel Prod Tests (${{ matrix.app.name }} - ${{ matrix.vm }}) runs-on: ubuntu-latest - timeout-minutes: 30 + # The quickjs lanes run the WASM engine and have outgrown 30 minutes as + # the suite grew (nextjs-turbopack quickjs was killed mid-final-group, + # all tests passing, on three consecutive attempts on 2026-08-18). + timeout-minutes: 40 needs: ci-scope if: ${{ needs.ci-scope.outputs.fast-path != 'true' }} permissions: diff --git a/packages/world-vercel/src/frames.ts b/packages/world-vercel/src/frames.ts index f5de347979..863d1cc0ca 100644 --- a/packages/world-vercel/src/frames.ts +++ b/packages/world-vercel/src/frames.ts @@ -66,23 +66,14 @@ export async function* decodeFrames( let buffer = new Uint8Array(0); const refill = async (needed: number): Promise => { - if (buffer.byteLength >= needed) return true; - - const parts: Uint8Array[] = [buffer]; - let byteLength = buffer.byteLength; - while (byteLength < needed) { - const chunk = await chunks.next(); - if (chunk.done) return false; - if (chunk.value.byteLength === 0) continue; - parts.push(chunk.value); - byteLength += chunk.value.byteLength; - } - - buffer = new Uint8Array(byteLength); - let offset = 0; - for (const part of parts) { - buffer.set(part, offset); - offset += part.byteLength; + while (buffer.byteLength < needed) { + const { done, value } = await chunks.next(); + if (done) return false; + if (!value || value.byteLength === 0) continue; + const next = new Uint8Array(buffer.byteLength + value.byteLength); + next.set(buffer, 0); + next.set(value, buffer.byteLength); + buffer = next; } return true; };