commit 9b0f8b17988c60dc0fe05d822d856789f8d3922b
parent 72ac50ae460720f6d3851696ef81c6f92453461a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 20:59:33 -0400
common, editor(e2e): a unit test pins the yield; the e2e budget is capped at 15 s (D0 review L5)
snapshotYield.test.ts: a setImmediate probe queued during the first chunk runs
before the second chunk's first unit (fails with the yield removed). The spec's
idle-floored budget stops at 15 s, and its header says what it pins.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
3 files changed, 54 insertions(+), 4 deletions(-)
diff --git a/common/controller/channelSnapshot.ts b/common/controller/channelSnapshot.ts
@@ -694,8 +694,9 @@ const SNAPSHOT_VIDEO_CONCURRENCY = 16;
const SNAPSHOT_YIELD_EVERY = 2 * SNAPSHOT_VIDEO_CONCURRENCY;
// `fn` over `items` in chunks of `size`, in order, one setImmediate between
-// chunks. The concurrency inside a chunk is whatever `fn` imposes.
-async function mapInYieldingChunks<T, R>(
+// chunks. The concurrency inside a chunk is whatever `fn` imposes. Exported for
+// snapshotYield.test.ts, which pins the yield.
+export async function mapInYieldingChunks<T, R>(
items: readonly T[],
size: number,
fn: (item: T) => Promise<R>,
diff --git a/common/controller/snapshotYield.test.ts b/common/controller/snapshotYield.test.ts
@@ -0,0 +1,39 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { mapInYieldingChunks } from "./channelSnapshot";
+
+// Run with:
+// pnpm --filter yt-dlp-transcript-common exec tsx --test controller/snapshotYield.test.ts
+//
+// THE WALK YIELDS BETWEEN CHUNKS (release 17 slice D0). A macrotask queued
+// while the first chunk runs — a request's I/O callback, here a setImmediate
+// probe — runs before the second chunk's first unit starts. Without the yield
+// every unit below would finish in microtasks before the probe ever ran.
+
+test("a macrotask queued during chunk 1 runs before chunk 2 starts", async () => {
+ const events: string[] = [];
+ const items = Array.from({ length: 64 }, (_, i) => i);
+ const out = await mapInYieldingChunks(items, 32, async (i) => {
+ if (i === 0) setImmediate(() => events.push("probe"));
+ events.push(`unit ${i}`);
+ await Promise.resolve();
+ return i * 2;
+ });
+ assert.deepEqual(out, items.map((i) => i * 2), "results in order");
+ const probe = events.indexOf("probe");
+ assert.ok(probe > events.indexOf("unit 31"), "after the first chunk");
+ assert.ok(probe < events.indexOf("unit 32"), "before the second chunk");
+});
+
+test("one chunk, no yield; an empty list, no call", async () => {
+ let calls = 0;
+ assert.deepEqual(
+ await mapInYieldingChunks([1, 2, 3], 32, async (i) => {
+ calls++;
+ return i;
+ }),
+ [1, 2, 3],
+ );
+ assert.equal(calls, 3);
+ assert.deepEqual(await mapInYieldingChunks([], 32, async (i: number) => i), []);
+});
diff --git a/editor/e2e/dashboard-answers.spec.ts b/editor/e2e/dashboard-answers.spec.ts
@@ -29,7 +29,14 @@ import {
// renders in 0.3 s on a quiet machine takes 2–4 s with nothing regenerating at
// all. What this pins is that the regeneration does not starve the pages, not
// how fast the machine is — so the idle measurement taken a moment before sets
-// the floor, and on a quiet machine the budget is simply 5 s.
+// the floor, and on a quiet machine the budget is simply 5 s. CAPPED AT 15 s:
+// on a machine so loaded that idle pages take over 5 s, the floor stops
+// growing, and the case fails rather than stretching to hide starvation.
+//
+// What the case pins, honestly: the serial queue (read from the jobs' own
+// records) and starvation at the scale of seconds. The yield between chunks is
+// pinned by a unit test (common/controller/snapshotYield.test.ts): on a dev
+// server under load `main`'s walk answered inside such a budget too.
const OPS_AUTH = { authorization: "Bearer test-worker-token" };
@@ -131,7 +138,10 @@ test("/ and /jobs answer while two large reports regenerate, one after the other
for (let i = 0; i < 3; i++) {
for (const path of ["/", "/jobs"]) idle.push(await timedGet(request, path));
}
- const budgetMs = Math.max(5_000, 3 * Math.max(...idle.map((s) => s.ms)));
+ const budgetMs = Math.min(
+ Math.max(5_000, 3 * Math.max(...idle.map((s) => s.ms))),
+ 15_000,
+ );
// "Update all reports", through the ops API: a refresh-report job per
// channel on the serial queue. The call answers when both have finished, so