diff --git a/js/net/src/lite/publisher.test.ts b/js/net/src/lite/publisher.test.ts index fe55979dcd..fea005feab 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"; @@ -1535,29 +1534,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 = 1_000; - const update = (update: bigint, maxDelay: number) => + const update = (update: bigint, maxDelay: number, 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); @@ -1574,11 +1569,14 @@ 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, 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 batch = await Writer.collect(Version.DRAFT_07, async (writer) => { + await update(2n, budget * 100, writer); + await update(3n, budget, writer); + }); + await sub.client.writer.write(batch); await flush(); sub.track.finish(); sub.track.close(); @@ -1586,7 +1584,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/js/net/src/lite/tracks.test.ts b/js/net/src/lite/tracks.test.ts index 4c0bb7f688..00cd804a60 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(); diff --git a/quest/m0/test-flakes-2/README.md b/quest/m0/test-flakes-2/README.md index c5df7b6d49..bfbe85ed3e 100644 --- a/quest/m0/test-flakes-2/README.md +++ b/quest/m0/test-flakes-2/README.md @@ -247,7 +247,6 @@ Public API: none. Wire: none. - [Fallback playback](/quest/m0/test-flakes-2/ietf22-fallback.md) - a browser viewer follows a fallback within the media lane's bound without dropping its session - [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 -- [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 - [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 - [moq-rs's Python TCP close test settles on a promised error](/quest/m0/test-flakes-2/tcp-close-unroutable.md) - the reader's error after an endpoint close is one the socket contract promises, and its `pytest.mark.skip` is removed - [js/net's lite-07 datagram queue test holds up under load](/quest/m0/test-flakes-2/datagram-queues.md) - the test waits for the first track's queue instead of asserting at once, 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 aaec59a6ef..0000000000 --- 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.