From 9e727f453c2b46a7ca80db3708b758320336898d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 3 Nov 2026 21:01:07 -0800 Subject: [PATCH 1/6] chore(quest): claim quest/m0/nightly/miri.md Quest-Claim: ac4f1ff84a56bbbe From e22dd62ae952db83cf28cd1fdbd2f9c0f9dbca9f Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 3 Nov 2026 21:04:26 -0800 Subject: [PATCH 2/6] fix(ci): install Rust sources for Nightly Miri Co-Authored-By: GPT-6.1 Sol --- .github/workflows/nightly.yml | 2 +- quest/m0/nightly/miri.md | 44 ++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e9f16cc96d1..d3e47336680 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -360,7 +360,7 @@ jobs: # Miri interprets kio's and moq-net's in-memory unsafe; too slow for the merge gate. - recipe: rs miri nightly: true - components: miri + components: miri, rust-src steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/quest/m0/nightly/miri.md b/quest/m0/nightly/miri.md index c4c5cfc991c..e194544e548 100644 --- a/quest/m0/nightly/miri.md +++ b/quest/m0/nightly/miri.md @@ -19,25 +19,31 @@ since the missing `rust-src` keeps the m0 Nightly red. The scheduled run of 2026-11-03 ([run 41656613559](https://github.com/moq-dev/moq/actions/runs/41656613559/job/125988511062)) still fails the same way. -Act on the first Nightly runs' timings, not ahead of them. Locally (24 -cores) `just rs miri` took 11.5 min wall: strict pass 118 tests in 109 s, -epoch pass 94 in 307 s. - -- No Nightly timing exists yet: the first run with the job - ([run 41483654426](https://github.com/moq-dev/moq/actions/runs/41483654426), - 2026-11-02) failed in 2 minutes before any test, as cargo-miri found no - `rust-src` ("given Rust source directory ... does not exist"). Install it - with the toolchain (`components: miri` in `.github/workflows/nightly.yml` - becomes `miri, rust-src`), then read the timings from the next run. -- `a_group_has_no_frame_cap` (`rs/moq-net/src/model/group.rs:5228` as of - be39188000) took 307 s alone, writing 10,000 one-byte frames. Under `cfg(miri)` - write fewer, still more than any former per-group frame cap, so the test - keeps its meaning. -- `rs/moq-net/src/coding/reader.rs:1232` builds its payload byte by byte - (`(0..SIZE).map(|i| (i % 251) as u8)`); switch to a cheaper fill if - Miri starts running that module. +The setup fix in [moq-dev/moq#7812](https://github.com/moq-dev/moq/pull/7812) +installs `miri, rust-src` together. The scheduled failure above is before +any test, so there is still no scheduled ARM timing evidence. Keep this +quest until a scheduled run containing that fix records the job duration, +strict and epoch pass durations, slowest test, and headroom against both +budgets. Do not dispatch Nightly by hand to obtain it. + +Act on those timings before optimizing. Earlier local measurements on 24 +cores took 11.5 min wall: strict pass 118 tests in 109 s, epoch pass 94 in +307 s. These are desktop measurements, not proof of the 4-core ARM budget. + +- `a_group_has_no_frame_cap` (`rs/moq-net/src/model/group.rs`) took 307 s + alone, writing 10,000 one-byte frames. If scheduled timings warrant a + smaller Miri workload, it must still exceed the former 8192-frame cap. +- `rs/moq-net/src/coding/reader.rs` builds its payload byte by byte + (`(0..SIZE).map(|i| (i % 251) as u8)`); consider a cheaper fill only if + Miri starts running that module and measurements justify it. - If the job nears 60 minutes, split the strict and epoch passes into - separate matrix entries (`.github/workflows/nightly.yml` Tests matrix, - `timeout-minutes: 60` at line 328) rather than raising the timeout. + separate matrix entries rather than raising the timeout. Preserve + nextest's 15-minute per-test cap. Public API: none. Wire: none. + +## Waiting + +- moq-dev/moq: After the setup fix lands, the next scheduled Nightly on + `main` must run `Tests (rs miri)` on `ubuntu-24.04-arm`. Read its log and + record the timings and headroom above; local runs do not clear this wait. From 6ec0b212c20df51bee145559530d0982546a9575 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 3 Nov 2026 21:06:53 -0800 Subject: [PATCH 3/6] fix(ci): include Rust sources in local Miri setup hints Co-Authored-By: GPT-6.1 Sol --- rs/justfile | 2 +- sh/rs/miri.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rs/justfile b/rs/justfile index a91f3240ca1..9a1f1d6ea92 100644 --- a/rs/justfile +++ b/rs/justfile @@ -337,7 +337,7 @@ play *args: doctest *args: bash sh/local.sh cargo test --locked --doc "$@" -# Needs nightly with the miri component; executes only in Nightly. See sh/rs/miri.sh +# Needs nightly with miri and rust-src; executes only in Nightly. See sh/rs/miri.sh # for what runs and why it splits in two. # Run kio's and moq-net's hand-written in-memory unsafe under Miri. diff --git a/sh/rs/miri.sh b/sh/rs/miri.sh index 32cdbf903bb..ff28157d603 100755 --- a/sh/rs/miri.sh +++ b/sh/rs/miri.sh @@ -22,14 +22,14 @@ if [[ "$(rustc --version)" == *-nightly* ]]; then elif command -v rustup >/dev/null 2>&1; then sysroot=$(rustup run nightly rustc --print sysroot) else - echo "rs: nightly Rust is missing; select it or run rustup toolchain install nightly --component miri" >&2 + echo "rs: nightly Rust is missing; select it or run rustup toolchain install nightly --component miri,rust-src" >&2 exit 1 fi # Nix puts its pinned stable compiler first; cargo and rustc must both be nightly. export PATH="$sysroot/bin:$PATH" if ! cargo miri --version >/dev/null 2>&1; then - echo "rs: Miri is missing: rustup component add --toolchain nightly miri" >&2 + echo "rs: Miri is missing: rustup component add --toolchain nightly miri rust-src" >&2 exit 1 fi rustc --version From 6f94ede941863efdb45cbc65a1364724aa175dca Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 10:00:20 -0800 Subject: [PATCH 4/6] chore(quest): claim quest/m0/nightly/miri.md Quest-Claim: 86bede8a02f900ff From f893e756daca5a7ad5ded5c7ddc9d8ac4bad9b2f Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 10:49:41 -0800 Subject: [PATCH 5/6] fix(kio): identify a batch's waker by its data pointer A RawWakerVTable is a promoted constant with no unique address, so a clone of an Arc-backed waker can carry a different vtable pointer than the original. Miri varies it on purpose, so Waiter::batch never matched its own task's set wakes and Nightly's Miri pass failed two kio task tests. Co-Authored-By: Claude Opus 5.5 --- rs/kio/src/task.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rs/kio/src/task.rs b/rs/kio/src/task.rs index 373cf6e239b..838da944839 100644 --- a/rs/kio/src/task.rs +++ b/rs/kio/src/task.rs @@ -53,7 +53,7 @@ use std::{ Arc, Mutex, atomic::{AtomicBool, AtomicU64, Ordering}, }, - task::{Context, Poll, RawWakerVTable, Wake, Waker}, + task::{Context, Poll, Wake, Waker}, }; use crate::waiter::{Park, Waiter}; @@ -169,11 +169,17 @@ std::thread_local! { static POLLING: Polling = const { Polling { open: RefCell::new(Vec::new()), held: RefCell::new(Vec::new()) } }; } -/// A waker's identity: what [`Waker::will_wake`] compares. -type Identity = (*const (), *const RawWakerVTable); +/// A waker's identity: its data pointer, the task it wakes. +/// +/// Not the vtable pointer [`Waker::will_wake`] also compares: a vtable is a promoted +/// constant with no unique address, so a clone of an `Arc`-backed waker may carry a +/// different one than the original (Miri varies it on purpose), and a batch would +/// miss its own task's wakes. A foreign waker sharing a batch's data pointer only +/// has its wake held until that batch returns. +type Identity = *const (); fn identity(waker: &Waker) -> Identity { - (waker.data(), waker.vtable()) + waker.data() } /// The [`Waiter::batch`]es running on this thread, and the wakes held for them. From 267a7a353d52b4ae0d8b75515fae87b86b50d2d0 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 12:29:13 -0800 Subject: [PATCH 6/6] test(net,kio): keep Nightly's Miri tests under the SLOW mark The scheduled Miri run took 372 s on a_delivery_attaching_mid_write_publishes_one_payload, half of it filling the payload byte by byte; repeat one period instead. a_large_task_set_counts_each_child_alone needs only one child past the sim's 4096 Ready limit, so name the limit and use it. a_group_has_no_frame_cap spends ~30 ms per write under Miri, so 10,000 runs past SLOW; it still runs natively in the merge gate. Rewrite the quest to the remaining wait: a scheduled run with both passes. Co-Authored-By: Claude Opus 5.5 --- quest/m0/nightly/README.md | 2 +- quest/m0/nightly/miri.md | 78 ++++++++++++++++++----------------- rs/kio/src/clock/sim.rs | 3 +- rs/kio/src/coop.rs | 6 ++- rs/moq-net/src/model/frame.rs | 5 ++- rs/moq-net/src/model/group.rs | 1 + 6 files changed, 53 insertions(+), 42 deletions(-) diff --git a/quest/m0/nightly/README.md b/quest/m0/nightly/README.md index 9b5b70d8b8d..f0dd5af4e5f 100644 --- a/quest/m0/nightly/README.md +++ b/quest/m0/nightly/README.md @@ -40,4 +40,4 @@ Public API: none. Wire: none. - [The relay's startup fits its stack under all features](/quest/m0/nightly/relay-stack.md) - `debug_relay_load_and_run_fit_windows_stack` passes under `--all-features`, fixed at the feature that grew the future - [moq-net's ingest fuzz target finds no stall](/quest/m0/nightly/ingest-fuzz.md) - the 13-byte stream that stalls ingest is fixed at its cause and kept as a regression test - [Nightly's shipping-size check passes](/quest/m0/nightly/shipping-size.md) - js/net, js/watch, and moq-relay growth over 5% is traced to its commits, then shrunk or accepted -- [Nightly's Miri job stays inside its time budget](/quest/m0/nightly/miri.md) - Miri gets `rust-src`, then finishes inside its 60-minute job and 15-minute-per-test budgets +- [Nightly's Miri job stays inside its time budget](/quest/m0/nightly/miri.md) - both Miri passes go green, with ARM timings inside the 60-minute job and 15-minute-per-test budgets diff --git a/quest/m0/nightly/miri.md b/quest/m0/nightly/miri.md index 2a2405fd837..b634711efee 100644 --- a/quest/m0/nightly/miri.md +++ b/quest/m0/nightly/miri.md @@ -1,9 +1,9 @@ -# [S] Nightly's Miri job stays inside its time budget +# [XS] Nightly's Miri job stays inside its time budget ## Goal -Nightly's `Tests (rs miri)` entry finishes well inside the job's 60-minute -timeout on the 4-core ARM runner, and no single Miri test comes near +Nightly's `Tests (rs miri)` entry passes and finishes well inside the job's +60-minute timeout on the 4-core ARM runner, and no single Miri test comes near nextest's 15-minute `miri` profile cap, with headroom recorded from a real Nightly run. @@ -11,45 +11,47 @@ Nightly run. Decided 2026-10-31 (user, /quest-plan of the follow-ups from the 2026-10-31 /quest-complete run), from [moq-dev/moq#7315](https://github.com/moq-dev/moq/pull/7315) (merged), which finished the Miri for in-memory unsafe quest. -Milestone m2 (user: "use recommended for any CI/flakes"). Decided 2026-11-03 (maintainer, /quest-plan of the follow-ups from [moq-dev/moq#7667](https://github.com/moq-dev/moq/pull/7667)): -moved from m2 under [Nightly passes on main](/quest/m0/nightly/README.md), -since the missing `rust-src` keeps the m0 Nightly red. The scheduled run of -2026-11-03 ([run 41656613559](https://github.com/moq-dev/moq/actions/runs/41656613559/job/125988511062)) -still fails the same way. - -The setup fix in [moq-dev/moq#7812](https://github.com/moq-dev/moq/pull/7812) -installs `miri, rust-src` together. The scheduled failure above is before -any test, so there is still no scheduled ARM timing evidence. Keep this -quest until a scheduled run containing that fix records the job duration, -strict and epoch pass durations, slowest test, and headroom against both -budgets. Do not dispatch Nightly by hand to obtain it. - -In the same log, confirm moq-net's `model::frame::` tests ran (the strict -pass selects them in `sh/rs/miri.sh`), including the lock-free single-delivery -path [moq-dev/moq#7508](https://github.com/moq-dev/moq/pull/7508) added; extend -the selection if any are missed. (Folded in from moq-dev/moq#7748's -`miri-frame.md`, 2026-11-04, whose `rust-src` half #7812 landed.) - -Act on those timings before optimizing. Earlier local measurements on 24 -cores took 11.5 min wall: strict pass 118 tests in 109 s, epoch pass 94 in -307 s. These are desktop measurements, not proof of the 4-core ARM budget. - -- `a_group_has_no_frame_cap` (`rs/moq-net/src/model/group.rs`) took 307 s - alone, writing 10,000 one-byte frames. If scheduled timings warrant a - smaller Miri workload, it must still exceed the former 8192-frame cap. -- `rs/moq-net/src/coding/reader.rs` builds its payload byte by byte - (`(0..SIZE).map(|i| (i % 251) as u8)`); consider a cheaper fill only if - Miri starts running that module and measurements justify it. -- If the job nears 60 minutes, split the strict and epoch passes into - separate matrix entries rather than raising the timeout. Preserve - nextest's 15-minute per-test cap. +moved under [Nightly passes on main](/quest/m0/nightly/README.md), since the +Miri job keeps the m0 Nightly red. Do not dispatch Nightly by hand for the +timings. + +The first scheduled run with `rust-src` ([moq-dev/moq#7812](https://github.com/moq-dev/moq/pull/7812)), +[run 41852517544](https://github.com/moq-dev/moq/actions/runs/41852517544/job/126652295876) +on 2026-11-04, ran only the strict pass: two kio `task::` tests failed, and +`sh/rs/miri.sh` stops before the epoch pass when one fails. +[moq-dev/moq#7895](https://github.com/moq-dev/moq/pull/7895) fixed them and +shrank the slowest Miri workloads. What that run recorded: + +- Job 19 min 22 s; tests start 6 min in (toolchain, cache, a 2 min 20 s build). +- Strict pass: 228 tests in 800 s (13.3 min). Slowest + `model::frame::tests::a_delivery_attaching_mid_write_publishes_one_payload` + 372 s (SLOW at 300 s; about half was its payload fill, now cheap), then kio's + `clock::sim::tests::a_large_task_set_counts_each_child_alone` 212 s (now one + child past the 4096 Ready limit instead of 10,000: 56 s locally, from 280 s) + and `task::tests::concurrent_wakes_are_never_lost` 188 s, against the 900 s cap. +- All 17 `model::frame::` tests ran and passed, including the two + single-delivery tests from [moq-dev/moq#7508](https://github.com/moq-dev/moq/pull/7508); + the selection needs no change. + +The epoch pass has no ARM timing yet. On a loaded 24-core desktop it took 370 s +for 97 tests, with `model::group::test::a_group_has_no_frame_cap` (10,000 +writes at about 30 ms each under Miri) past SLOW; #7895 ignores that test under +Miri, since it still runs natively in the merge gate. + +Remaining: read the next scheduled run's log and record the job duration, both +pass durations, the slowest test, and headroom against both budgets. If the job +nears 60 minutes, split the strict and epoch passes into separate matrix +entries rather than raising the timeout, and keep nextest's 15-minute per-test +cap. A test past SLOW gets a smaller Miri workload. Public API: none. Wire: none. ## Waiting -- moq-dev/moq: After the setup fix lands, the next scheduled Nightly on - `main` must run `Tests (rs miri)` on `ubuntu-24.04-arm`. Read its log and - record the timings and headroom above; local runs do not clear this wait. +- moq-dev/moq: After [moq-dev/moq#7895](https://github.com/moq-dev/moq/pull/7895) + lands, the next scheduled Nightly on `main` must pass `Tests (rs miri)` on + `ubuntu-24.04-arm` with both passes. Read its log + (`gh run view --repo moq-dev/moq --log`) and record the timings above; + local runs do not clear this wait. diff --git a/rs/kio/src/clock/sim.rs b/rs/kio/src/clock/sim.rs index 4cdb20b036d..9e5097f5fca 100644 --- a/rs/kio/src/clock/sim.rs +++ b/rs/kio/src/clock/sim.rs @@ -744,11 +744,12 @@ mod tests { run(async {}); } + /// One child more than the limit, so counting the set as one poll would trip it. #[crate::test] async fn a_large_task_set_counts_each_child_alone() { let producer = crate::Producer::new(()); let mut tasks = crate::Tasks::new(); - for _ in 0..10_000 { + for _ in 0..=crate::coop::READY_LIMIT { let consumer = producer.consume(); tasks.push(move |waiter: &crate::Waiter| consumer.poll(waiter, |_| Poll::Ready(())).map(drop)); } diff --git a/rs/kio/src/coop.rs b/rs/kio/src/coop.rs index 3b04ac7f69b..f8449aaa01c 100644 --- a/rs/kio/src/coop.rs +++ b/rs/kio/src/coop.rs @@ -9,6 +9,10 @@ use std::task::Poll; use crate::Waiter; +/// Ready kio polls one task poll may make under the simulator before it panics as a spin. +#[cfg(feature = "sim")] +pub(crate) const READY_LIMIT: usize = 4096; + #[cfg(feature = "sim")] thread_local! { static READY: std::cell::Cell> = const { std::cell::Cell::new(None) }; @@ -47,7 +51,7 @@ pub(crate) fn ready(value: T) -> Poll { let count = READY.get().map(|count| count + 1); READY.set(count); assert!( - count.is_none_or(|count| count <= 4096), + count.is_none_or(|count| count <= READY_LIMIT), "kio Ready poll limit exceeded at {}; add a coop::Budget to the loop", std::panic::Location::caller(), ); diff --git a/rs/moq-net/src/model/frame.rs b/rs/moq-net/src/model/frame.rs index e5a2c09a10d..8e25353d364 100644 --- a/rs/moq-net/src/model/frame.rs +++ b/rs/moq-net/src/model/frame.rs @@ -2020,7 +2020,10 @@ mod tests { #[test] fn a_delivery_attaching_mid_write_publishes_one_payload() { const SIZE: usize = SEGMENT + 4093; - let payload: Vec<_> = (0..SIZE).map(|i| (i % 251) as u8).collect(); + // Repeating one period spares Miri a fill it would step through byte by byte, + // half this test's time there. + let mut payload = (0..251).collect::>().repeat(SIZE.div_ceil(251)); + payload.truncate(SIZE); for grow in [false, true] { let budget = Budget::new(if grow { 0 } else { SEGMENT }); let (a, active) = FrameBuf::admitted(SIZE, &budget); diff --git a/rs/moq-net/src/model/group.rs b/rs/moq-net/src/model/group.rs index f5bfff14221..bf341b3a89c 100644 --- a/rs/moq-net/src/model/group.rs +++ b/rs/moq-net/src/model/group.rs @@ -5310,6 +5310,7 @@ mod test { /// A frame count past the old 8192-frame cap is held whole while it fits the tail. #[test] + #[cfg_attr(miri, ignore = "each write takes Miri ~30 ms, so 10,000 run past its SLOW mark")] fn a_group_has_no_frame_cap() { let mut producer = Info { sequence: 0 }.produce(); for _ in 0..10_000 {