diff --git a/quest/m1/test-flakes-2/README.md b/quest/m1/test-flakes-2/README.md index 2bfe2d0cb2..d2af7332f4 100644 --- a/quest/m1/test-flakes-2/README.md +++ b/quest/m1/test-flakes-2/README.md @@ -33,7 +33,6 @@ Public API: none. Wire: none. ## Required - [Interop close code](/quest/m1/test-flakes-2/interop-close-code.md) - the browser close-code test reliably sees `unauthorized` for a refused session -- [TS passthrough jump](/quest/m1/test-flakes-2/ts-passthrough-jump.md) - moq-cli's flagged-jump TS passthrough test passes under load without wall-clock pacing - [QUIC-wins race](/quest/m1/test-flakes-2/quic-winning-race.md) - moq-tokio's QUIC-wins test sets up QUIC's win instead of racing the WebSocket head start ## Related diff --git a/quest/m1/test-flakes-2/ts-passthrough-jump.md b/quest/m1/test-flakes-2/ts-passthrough-jump.md deleted file mode 100644 index 85dc026ab1..0000000000 --- a/quest/m1/test-flakes-2/ts-passthrough-jump.md +++ /dev/null @@ -1,25 +0,0 @@ -# [S] TS passthrough jump test holds under load - -## Goal - -`publish::tests::ts_passthrough_crosses_a_relay_through_a_flagged_jump` -(`rs/moq-cli`) passes under a loaded `just check`, fixed at its cause. - -## Plan - -Seen 2026-10-09 by three PRs on unmodified `main` (#5146, #5155, #5153): the -assertion "moq-transport-14: both copies crossed" fails, so the recording -held at most one copy of the fixture. It failed 2 of 3 loaded runs and -passes alone. The test came in with #5003. It paces its input with a 15 ms -wall-clock sleep and waits up to 10 s. - -Decided 2026-10-09: find why the second copy goes missing under load before -changing anything, then replace the wall-clock pacing with mocked time or -observable events where the fixture allows. Never widen the wait or add a -retry, per this questline's rules. - -Public API: none. Wire: none. - -## Required - -- [The moq-time crate](/quest/m1/time/crate.md) - the controlled clock and sim this tests on diff --git a/rs/moq-cli/src/publish.rs b/rs/moq-cli/src/publish.rs index 6dc2b6b759..7793d78dc2 100644 --- a/rs/moq-cli/src/publish.rs +++ b/rs/moq-cli/src/publish.rs @@ -1019,14 +1019,21 @@ pub(crate) mod tests { .expect("a catalog"); let name = catalog.m2ts.expect("the m2ts section").track; + // Subscribe and take the first group, so a read starts behind anything decoded + // afterwards: an IETF subscription joins the publisher's current group when its + // SUBSCRIBE arrives, not when it is sent. + let start = async |subscription: moq_net::track::Subscription| { + let mut track = consumer.track(&name).unwrap().subscribe(subscription).await.unwrap(); + let first = tokio::time::timeout(WAIT, track.recv_group()).await.unwrap().unwrap(); + (track, first) + }; // Each group's sequence and frames, read until the track ends. A read cut off as // stale leaves `None` for that group's frames. - let read = |subscription: moq_net::track::Subscription| { - let track = consumer.track(&name).unwrap(); + let read = |(mut track, first): (moq_net::track::Subscriber, Option)| { tokio::spawn(tokio::time::timeout(WAIT, async move { - let mut track = track.subscribe(subscription).await.unwrap(); let mut groups = Vec::new(); - while let Some(mut group) = track.recv_group().await.unwrap() { + let mut next = first; + while let Some(mut group) = next { let mut frames = Some(Vec::new()); loop { match group.read_frame().await { @@ -1044,17 +1051,16 @@ pub(crate) mod tests { } } groups.push((group.sequence, frames)); + next = track.recv_group().await.unwrap(); } groups.sort_by_key(|(sequence, _)| *sequence); groups })) }; - let recording = read(moq_net::track::Subscription::default().with_max_delay(RECORDING_MAX_AGE)); - // About twice real time, so the recording keeps up and only staleness can cut it. - for chunk in second.chunks(8 * 1024) { - decoder.decode_chunk(chunk).unwrap(); - tokio::time::sleep(Duration::from_millis(15)).await; - } + let recording = + read(start(moq_net::track::Subscription::default().with_max_delay(RECORDING_MAX_AGE)).await); + // All at once: the track ends while the relay is still pulling the older groups. + decoder.decode_chunk(&second).unwrap(); decoder.finish().unwrap(); let recording: Vec<_> = recording @@ -1091,10 +1097,11 @@ pub(crate) mod tests { // the budget behind. An IETF subscription starts at the newest group, so only lite // has the groups behind it to read. if version.starts_with("moq-lite") { - let late = read(moq_net::track::Subscription::default().with_max_delay(Duration::from_secs(10))) - .await - .unwrap() - .expect("the late read ends with the track"); + let late = + read(start(moq_net::track::Subscription::default().with_max_delay(Duration::from_secs(10))).await) + .await + .unwrap() + .expect("the late read ends with the track"); let whole: Vec = late .iter() .filter(|(_, frames)| frames.is_some())