Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion quest/m0/nightly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
78 changes: 40 additions & 38 deletions quest/m0/nightly/miri.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,57 @@
# [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.

## Plan

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 <run> --repo moq-dev/moq --log`) and record the timings above;
local runs do not clear this wait.
3 changes: 2 additions & 1 deletion rs/kio/src/clock/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
6 changes: 5 additions & 1 deletion rs/kio/src/coop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<usize>> = const { std::cell::Cell::new(None) };
Expand Down Expand Up @@ -47,7 +51,7 @@ pub(crate) fn ready<T>(value: T) -> Poll<T> {
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(),
);
Expand Down
14 changes: 10 additions & 4 deletions rs/kio/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion rs/moq-net/src/model/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<u8>>().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);
Expand Down
1 change: 1 addition & 0 deletions rs/moq-net/src/model/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading