From 90be2252587dbb52434e35a92f12736b82dc39dd Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 9 Nov 2026 01:05:06 -0800 Subject: [PATCH 1/4] chore(quest): claim quest/m0/test-flakes-2/redropped-reoffer.md Quest-Claim: e8ac9a04d3838ec9 From 1ae913bb3d6df34fcc412df1a93a2e157f5f64c3 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 9 Nov 2026 01:34:09 -0800 Subject: [PATCH 2/4] test(net): order the re-dropped re-offer test's updates by one chunk, not yields Updates 2 and 3 arrive in one write, so the publisher decodes update 3 before the task boundary it yields after applying update 2, and so before the re-offer of group 1 can be popped. Unquarantines the test. Co-Authored-By: Claude Opus 5.5 --- js/net/src/lite/publisher.test.ts | 30 ++++++++++----------- quest/m0/test-flakes-2/README.md | 1 - quest/m0/test-flakes-2/redropped-reoffer.md | 29 -------------------- 3 files changed, 15 insertions(+), 45 deletions(-) delete mode 100644 quest/m0/test-flakes-2/redropped-reoffer.md diff --git a/js/net/src/lite/publisher.test.ts b/js/net/src/lite/publisher.test.ts index d420fa1b686..435f31a0802 100644 --- a/js/net/src/lite/publisher.test.ts +++ b/js/net/src/lite/publisher.test.ts @@ -15,7 +15,6 @@ import { Front } from "../resume.ts"; import { Reader, Stream, Writer, writeBudget } from "../stream.ts"; import { Timescale, Timestamp } from "../time.ts"; import { Subscriber as TrackSubscriber } from "../track.ts"; -import { Budget } from "../util/yield.ts"; import { wireOf } from "../wire.ts"; import { type AnnounceBroadcast, AnnounceOk, AnnounceRequest, decodeAnnounceBroadcast } from "./announce.ts"; import { Fetch } from "./fetch.ts"; @@ -1523,29 +1522,25 @@ test("lite draft-07: a final drop replaces an expired stream", async () => { // A queued re-offer that a later update drops again as stale counts as given up: the track's end // leaves that stale drop standing, rather than replacing it with a final one. Mirrors the Rust // `a_redropped_reoffer_stays_stale_at_the_end_on_lite07`. -// Quarantined: flaky under load, see /quest/m0/test-flakes-2/redropped-reoffer.md -test.skip("lite draft-07: a re-offer dropped again before it opens stays stale at the end", async () => { +test("lite draft-07: a re-offer dropped again before it opens stays stale at the end", async () => { const clock = new Manual(); const installed = clock.install(); - // A write's budget yields on host CPU time, which could run the publisher's post-update task - // before the test writes update 3. - const poll = spyOn(Budget.prototype, "poll").mockReturnValue(undefined); // Well inside the cache window, so the expired group is still cached to re-offer. const budget = Milli(1_000); - const update = (update: bigint, maxDelay: Milli) => + const update = (update: bigint, maxDelay: Milli, writer: Writer) => new SubscribeUpdate({ priority: 1, startGroup: 0n, update, maxDelay, ...restate(Version.DRAFT_07, update - 1n), - }).encode(sub.client.writer, Version.DRAFT_07); + }).encode(writer, Version.DRAFT_07); const sub = await servedSubscription({ version: Version.DRAFT_07, startGroup: 0n }); try { sub.serve(0n); expect(await sub.servedSequence()).toBe(0n); expect("start" in (await decodeSubscribeResponse(sub.client.reader, Version.DRAFT_07))).toBe(true); - await update(1n, budget); + await update(1n, budget, sub.client.writer); // The restated range resolves at once. expect("started" in (await decodeSubscribeResponse(sub.client.reader, Version.DRAFT_07))).toBe(true); @@ -1562,11 +1557,17 @@ test.skip("lite draft-07: a re-offer dropped again before it opens stays stale a if (!("drop" in first)) throw new Error("expected SUBSCRIBE_DROP"); expect([first.drop.start, first.drop.end, first.drop.update]).toEqual([1n, 1n, 1n]); - // Update 2 widens the budget, so group 1 is re-offered. Update 3 restores it within the - // task the publisher yields after an update, before that re-offer opens. - await update(2n, Milli(budget * 100)); - await flush(); - await update(3n, budget); + // Update 2 widens the budget, so group 1 is re-offered; update 3 restores it. Both arrive in + // one chunk, which the decoder reads without leaving the task, so update 3 is decoded + // before the task boundary the publisher yields after an update, and so before any pop. + const chunks: Uint8Array[] = []; + const batch = new Writer( + new WritableStream({ write: (chunk) => void chunks.push(chunk.slice()) }), + Version.DRAFT_07, + ); + await update(2n, Milli(budget * 100), batch); + await update(3n, budget, batch); + await sub.client.writer.write(new Uint8Array(chunks.flatMap((chunk) => [...chunk]))); await flush(); sub.track.finish(); sub.track.close(); @@ -1574,7 +1575,6 @@ test.skip("lite draft-07: a re-offer dropped again before it opens stays stale a } finally { await sub.close(); installed.close(); - poll.mockRestore(); } }); diff --git a/quest/m0/test-flakes-2/README.md b/quest/m0/test-flakes-2/README.md index 8e83f1a21a4..14306cd0f62 100644 --- a/quest/m0/test-flakes-2/README.md +++ b/quest/m0/test-flakes-2/README.md @@ -243,6 +243,5 @@ Public API: none. Wire: none. - [js/net's bandwidth sampling test holds up under load](/quest/m0/test-flakes-2/bandwidth-sampling.md) - the sampling test counts ticks without racing the mocked clock, and its `test.skip` is removed - [bench/relay.test.py's stream-transport test gets a port free for TCP and UDP](/quest/m0/test-flakes-2/relay-port-pair.md) - the test binds TCP and UDP to one number free in both, so parallel checks can't collide with it - [js/net's compressed-frame cap test holds up under load](/quest/m0/test-flakes-2/compressed-frame-cap.md) - the cap is proven without a 32 MiB payload, and its `test.skip` is removed -- [js/net's lite-07 re-dropped re-offer test holds up under load](/quest/m0/test-flakes-2/redropped-reoffer.md) - update 3 is ordered before the re-offer opens by an event, not a yield count, and its `test.skip` is removed - [moq-net's random-mesh withdrawal tests fit the gate](/quest/m0/test-flakes-2/mesh-withdraw-runtime.md) - the 200-seed mesh tests cost seconds of CPU, and their `#[ignore]` is removed - [js/net's datagram session-close-after-end test holds up under load](/quest/m0/test-flakes-2/datagram-close-end.md) - the abort is ordered after the end by an event the client observes, and its `test.skip` is removed diff --git a/quest/m0/test-flakes-2/redropped-reoffer.md b/quest/m0/test-flakes-2/redropped-reoffer.md deleted file mode 100644 index aaec59a6ef2..00000000000 --- a/quest/m0/test-flakes-2/redropped-reoffer.md +++ /dev/null @@ -1,29 +0,0 @@ -# [S] js/net's lite-07 re-dropped re-offer test holds up under load - -## Goal - -`js/net/src/lite/publisher.test.ts`'s "lite draft-07: a re-offer dropped -again before it opens stays stale at the end" passes on a loaded host, and -its `test.skip` is removed. Update 3 is guaranteed to land before the -re-offer of group 1 opens, so the end still reports group 1 as stale. - -## Plan - -Planned 2026-11-09 (maintainer, /quest-complete), under the repository's -flaky-test rule. Quarantined by -[moq-dev/moq#8333](https://github.com/moq-dev/moq/pull/8333) after the whole -`js/net` package failed it 1 of 30 times under 48 CPU burners on 24 cores, -first seen in [moq-dev/moq#8326](https://github.com/moq-dev/moq/pull/8326)'s -loaded runs. - -The failure: after 22 ms, `holesToEnd` returned `[]` where -`[["drop", 1n, 1n, 3n]]` was expected (`publisher.test.ts:1572`). Suspected, -not confirmed: the write budget is already stubbed, but the test sends update -2, waits one `yieldNow()`, then sends update 3, assuming update 3 lands -before the re-offer opens; under load the re-offer is served first. Reproduce -it first. Then order the test on an event (the update's reply or the -re-offer's queueing), not a count of yields. If the publisher can serve a -re-offer an already-sent update dropped, that is a publisher bug: fix it there, -mirroring Rust's `a_redropped_reoffer_stays_stale_at_the_end_on_lite07`. - -Public API: none. Wire: none. From 3d69c25ad021edacd28bde005d76514b064dee87 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 9 Nov 2026 01:36:50 -0800 Subject: [PATCH 3/4] test(net): quarantine the listing-churn flood test, flaky under load Co-Authored-By: Claude Opus 5.5 --- js/net/src/lite/tracks.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/net/src/lite/tracks.test.ts b/js/net/src/lite/tracks.test.ts index 3ebc1f5db09..1acad85262f 100644 --- a/js/net/src/lite/tracks.test.ts +++ b/js/net/src/lite/tracks.test.ts @@ -250,7 +250,8 @@ test("a listing past the announces cap is reset, and the session stays", async ( again.close(); }); -test("a flood of a peer's listing churn yields to the host's tasks", async () => { +// Quarantined: timed out at 5 s in 2 of 30 full js/net runs under 48 CPU burners; see moq-dev/moq#8355. +test.skip("a flood of a peer's listing churn yields to the host's tasks", async () => { const { subscriber, listed } = handPlayed(); const tracks = subscriber.consume(Path.from("room")).tracks(); const peer = await listed(); From db40a0f46889b07fb943177fa45754db5145e53c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:25:58 -0800 Subject: [PATCH 4/4] test(net): batch the re-offer updates with Writer.collect Co-Authored-By: Claude Opus 5.5 --- js/net/src/lite/publisher.test.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/js/net/src/lite/publisher.test.ts b/js/net/src/lite/publisher.test.ts index 435f31a0802..97e34b91f3a 100644 --- a/js/net/src/lite/publisher.test.ts +++ b/js/net/src/lite/publisher.test.ts @@ -1560,14 +1560,11 @@ test("lite draft-07: a re-offer dropped again before it opens stays stale at the // Update 2 widens the budget, so group 1 is re-offered; update 3 restores it. Both arrive in // one chunk, which the decoder reads without leaving the task, so update 3 is decoded // before the task boundary the publisher yields after an update, and so before any pop. - const chunks: Uint8Array[] = []; - const batch = new Writer( - new WritableStream({ write: (chunk) => void chunks.push(chunk.slice()) }), - Version.DRAFT_07, - ); - await update(2n, Milli(budget * 100), batch); - await update(3n, budget, batch); - await sub.client.writer.write(new Uint8Array(chunks.flatMap((chunk) => [...chunk]))); + const batch = await Writer.collect(Version.DRAFT_07, async (writer) => { + await update(2n, Milli(budget * 100), writer); + await update(3n, budget, writer); + }); + await sub.client.writer.write(batch); await flush(); sub.track.finish(); sub.track.close();