Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d1c4e5b
fix(publish): wait for video dimensions before showing previews (#7926)
shermerL Nov 5, 2026
4216175
fix(video): time out V4L2 camera reads so a stalled camera can be dro…
Frando Nov 5, 2026
60eed56
feat(net): origins prove a route key in lite-07 SETUP (#7937)
kixelated Nov 5, 2026
7e62162
docs(agents): never reserve a huge value as a marker (#7974)
kixelated Nov 5, 2026
748e29f
fix(video): share bounded H.265 SPS parsing (#7841)
kixelated Nov 5, 2026
47dbef8
test: keep release registry metadata lookup offline (#7950)
kixelated Nov 5, 2026
8591ec3
feat(c)!: expose pre-encoded simulcast rate control (#7947)
kixelated Nov 5, 2026
1d40769
fix(net): the ingest fuzz target finds no stall (#7808)
kixelated Nov 5, 2026
99d5057
fix(msf)!: share resolved init data across tracks (#7949)
kixelated Nov 5, 2026
c3ab5bf
Agent build output can't fill the desktop's disk (#7960)
kixelated Nov 5, 2026
3a07cac
quest: plan audio language importers (#7964)
kixelated Nov 5, 2026
f9142ca
feat(hang)!: catalog includes on the root timeline (#7943)
kixelated Nov 5, 2026
7a83632
fix(mux): recover underestimated TS mux rates (#7967)
kixelated Nov 5, 2026
a14995f
fix(mux,quic): clear the fmp4 and connection fuzz findings (#7961)
kixelated Nov 5, 2026
acd799e
chore(quest): claim quest/m0/nightly/macos.md
kixelated Nov 5, 2026
9d6ff11
fix(audio): drive the capture gap test on its local task
kixelated Nov 5, 2026
ac64fc0
docs(quest): explain the synthetic non-Send alternative
kixelated Nov 5, 2026
5d03931
fix(dev): keep the dev shell out of the MOQ_* namespace (#7979)
kixelated Nov 5, 2026
83d0bc4
ci: run kio tests in the macOS suite (#7955)
kixelated Nov 5, 2026
6b00852
ci: cache Smoke's Nix runtime packages on main (#7662)
kixelated Nov 5, 2026
f53e495
feat(net): match Rust WebSocket receive windows (#7939)
kixelated Nov 5, 2026
70789be
feat(net)!: specify the lite-07 route wire and decode its messages (#…
kixelated Nov 5, 2026
6f92d44
chore(runner)!: remove the runner-migrate disk tool (#7985)
kixelated Nov 5, 2026
068ddc9
chore(deps): Bump the nix group with 2 updates (#7910)
dependabot[bot] Nov 5, 2026
f3ddaee
feat!: push-to-talk suppresses silence instead of toggling the catalo…
kixelated Nov 5, 2026
bec817f
fix(ci): report independent Nightly failures together (#7667)
kixelated Nov 5, 2026
6883a89
quest: audit m0 and m1 (2026-11-05) (#7975)
kixelated Nov 5, 2026
d6fcf6d
feat!: one Route handle owns its broadcast (#7806)
kixelated Nov 5, 2026
89d5905
quest: unblock m0 and m1 (2026-11-05) (#7982)
kixelated Nov 5, 2026
597fe8b
fix(audio): replay only the retained audio a subscriber's max delay w…
kixelated Nov 5, 2026
b67b1da
bench: calibrate the paired Nightly comparison from A/A and slowed-be…
kixelated Nov 6, 2026
5480d51
feat(ffi)!: expose socket endpoints and preserve IETF starts (#7883)
kixelated Nov 6, 2026
e419224
fix(net): a replacement that fails before delivering never ends a liv…
kixelated Nov 6, 2026
37f8305
quest: re-cut milestones by kind of change and priority (#7988)
kixelated Nov 6, 2026
b891adb
feat(net)!: add JavaScript socket endpoints and bound track requests …
kixelated Nov 6, 2026
0672ad5
fix(net)!: route a datagram that shares a packet with its answer or i…
kixelated Nov 6, 2026
7f0433b
Merge remote-tracking branch 'origin/quest/m0/nightly/macos' into t3/…
kixelated Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ set working-directory := '..'
cache:
bash sh/gh/cache.sh

# Publish Smoke's runtime packages and their build artifacts to nix.moq.dev.
cache-runtime:
bash sh/gh/cache-runtime.sh

# Build the runner image with a temporary policy on a disposable hosted runner.
runner-vm-ci:
#!/usr/bin/env bash
Expand Down
29 changes: 28 additions & 1 deletion .github/workflows/cache.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Cache

# Publish development environments independently of the landing worker's local caches.
# Publish dev shells and Smoke's runtime packages independently of the landing worker's local caches.
permissions:
contents: read

Expand Down Expand Up @@ -33,3 +33,30 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.NIX_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NIX_R2_SECRET_ACCESS_KEY }}
run: just gh cache

runtime:
name: Runtime (ubuntu-24.04)
if: github.ref == 'refs/heads/main'
# Smoke's only current nix platform. Keep its Rust builds independent of
# the dev-shell matrix and use Smoke's cold-build budget.
runs-on: ubuntu-24.04
timeout-minutes: 75
environment: cache-write
# Finish the active publication; new pushes replace only the pending run.
# Canceling active builds could starve cold dependency uploads on busy main.
concurrency:
group: cache-runtime-${{ github.ref }}
cancel-in-progress: false

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/nix
- uses: ./.github/actions/just
- name: Cache runtime packages and dependencies
env:
NIX_SIGNING_KEY: ${{ secrets.NIX_SIGNING_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.NIX_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NIX_R2_SECRET_ACCESS_KEY }}
run: just gh cache-runtime
18 changes: 13 additions & 5 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,15 @@ jobs:
artifact-path: target/bench/nightly/rust-bench
benchmark: true
paired: true
# Report-only until quest/m1/bench-ci.md fixes the build bias behind
# an A/A false alarm; the site still lists its confirmed regressions.
- name: Rust shared bench targets
recipe: gh nightly rust-shared
artifact: bench-rust-shared
artifact-path: target/bench/nightly/rust-shared
benchmark: true
paired: true
report-only: true
- name: Relay session benchmarks
recipe: gh nightly rust-relay
artifact: bench-rust-relay
Expand Down Expand Up @@ -235,7 +238,7 @@ jobs:
# After the upload, so the benchmark site still records tonight's history and
# publishes the comparison that failed this job before the alert links to it.
- name: Fail on paired regressions
if: ${{ matrix.paired && steps.workload.outcome == 'success' }}
if: ${{ matrix.paired && !matrix.report-only && steps.workload.outcome == 'success' }}
run: nix develop --command just gh benchmark-report gate "${{ matrix.artifact-path }}"

# gh-pages is the one Pages site, and each directory has one writer: this job owns
Expand Down Expand Up @@ -466,30 +469,35 @@ jobs:
persist-credentials: false
- uses: ./.github/actions/nix
- name: Rust cache
id: setup
uses: ./.github/actions/rust-cache
with:
access-key-id: ${{ secrets.MBX_R2_ACCESS_KEY_ID }}
secret-access-key: ${{ secrets.MBX_R2_SECRET_ACCESS_KEY }}
# Independent checks keep running after a failure; the job still finishes red.
- name: WebSocket fallback startup benchmark
run: nix develop --command just js qmux-bench

- name: Browser publisher fairness benchmark
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just js serve-bench
shell: bash -leo pipefail {0}
- name: Raw audio encoding benchmark
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just js raw-audio-bench
shell: bash -leo pipefail {0}
- name: Audio ring benchmark
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just js ring-bench
shell: bash -leo pipefail {0}
- name: Native headless playback
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just test native-play
shell: bash -leo pipefail {0}
env:
HARNESS_RUNS: ${{ runner.temp }}/moq-test-runs
- name: Audio quality
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just test audio-quality --runtimes chromium --enforce --capture
shell: bash -leo pipefail {0}
env:
Expand All @@ -508,7 +516,7 @@ jobs:
# stall voids about one AAC row in seven, which would fail most nights until
# it is fixed. Kept beside the browser run so its ceilings can be re-measured.
- name: Native audio quality
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
run: nix develop --command just test audio-quality --runtimes native
shell: bash -leo pipefail {0}
env:
Expand All @@ -517,7 +525,7 @@ jobs:
# The run directory is the diagnosis: each process's log, the shaper's
# counters, the raw samples, and every row's summary.
- name: Keep the run
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.setup.outcome == 'success' }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: audio-quality-run
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,16 @@ jobs:
- name: Setup complete
id: setup
run: echo "Platform tools ready"
- name: Rust compile and tests
- name: Rust compile
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.rs == 'true') }}
shell: bash
env:
CI_BASE: ${{ github.event_name != 'pull_request' && '--all' || '' }}
run: |
just rs windows "$CI_BASE"
just rs windows-test
run: just rs windows "$CI_BASE"
- name: Rust tests
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.rs == 'true') }}
shell: bash
run: just rs windows-test
- name: C
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.c == 'true') }}
shell: bash
Expand Down Expand Up @@ -159,13 +161,14 @@ jobs:
- name: Setup complete
id: setup
run: echo "Platform tools ready"
- name: Rust compile and tests
- name: Rust compile
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.rs == 'true') }}
env:
CI_BASE: ${{ github.event_name != 'pull_request' && '--all' || '' }}
run: |
just rs macos "$CI_BASE"
just rs macos-test
run: just rs macos "$CI_BASE"
- name: Rust tests
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.rs == 'true') }}
run: just rs macos-test
- name: Swift simulator check
if: ${{ !cancelled() && steps.setup.outcome == 'success' && (needs.scope.outputs.swift == 'true') }}
run: just swift simulator
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ jobs:
runs-on: ${{ matrix.os }}
# The matrix runs in real time (ffmpeg -re pacing + per-subscriber timeouts);
# this is just a safety net against a hung relay/client. The nix channel
# gets longer: nix.moq.dev serves the toolchain but not moq's packages at
# HEAD, and compiling them has taken 43 minutes.
# gets longer for a cold build when Cache has not finished publishing
# HEAD's packages yet; compiling them has taken 43 minutes.
timeout-minutes: ${{ matrix.channel == 'nix' && 75 || 45 }}

# smoke.sh resolves the latest libmoq release via the GitHub API. Without a
Expand Down Expand Up @@ -74,7 +74,8 @@ jobs:
- os: ubuntu-latest
channel: docker
# macOS cells temporarily disabled to cut CI spend (over budget this
# month). Re-enable by uncommenting; nothing else depends on them.
# month). Re-enable by uncommenting; the nix cell also needs its
# platform added to cache.yml's runtime job.
# - os: macos-latest
# channel: cargo
# - os: macos-latest
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The API is the most important thing to get right: a bad shape costs a breaking c
- Propose alternatives when a name or shape feels awkward.
- Short names under a module namespace (`encode::Config`, not `EncoderConfig`), mirrored across Rust, JS, and the bindings.
- Keep package boundaries modular and reusable; prefer a module in an existing package over a one-off package.
- Never use a huge value like `u64::MAX` as a marker (it is the longest varint); use 0, a separate message or type, or a boolean.

# Development

Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 32 additions & 5 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ also run during timing. Every timed round reads and checks all payload bytes,
returns lease credit, and fills each stream again. Throughput counts delivered
payload once. Nightly's supplemental Rust benchmark leg records the series and
keeps the memory output with its raw results. The broader MoQ session transport
and version sweep remains in [its quest](/quest/m2/bench-transport-sweep.md).
and version sweep remains in [its quest](/quest/m4/bench-transport-sweep.md).

Compare one multi-threaded Tokio runtime with the same number of independent
Tokio/epoll and io\_uring workers:
Expand Down Expand Up @@ -890,8 +890,8 @@ the table requires no new renderer or data branch. A source's `paired.json`, as

### Paired comparison

Each Nightly's Rust bench groups (`rust-bench`, `rust-shared`, `rust-extra` and
`rust-uring`) also build the previous scheduled Nightly's commit beside their
Each Nightly's Rust bench groups (`rust-bench`, `rust-shared`, `rust-relay`,
`rust-extra` and `rust-uring`) also build the previous scheduled Nightly's commit beside their
own, on the same runner, so night-to-night runner variance drops out. Each
built target runs once per side in the trend's quick mode, alternating which
side goes first; the current side is the trend point. A case more than 10%
Expand All @@ -901,8 +901,21 @@ measurement, 30 samples). A case whose rounds are all slower with a median
beyond 10% is a confirmed regression and fails its job after the upload, so the
Alert workflow posts the failed Nightly. A target the base cannot build or run
fails the job too, rather than passing uncompared; a bench target the base does
not have yet is only reported as new. The threshold is a starting point until
A/A runs on the hosted runner measure its noise floor.
not have yet is only reported as new. `rust-shared` is report-only for now: its
confirmed regressions show on the site but do not fail the job, until the
build bias below is fixed.

Runs on the hosted runner set these numbers. In four A/A runs of the
`rust-shared`, `rust-extra` and `rust-uring` groups, and two of `rust-relay`,
1 to 9% of cases crossed 10% either way in the screen, at most 17 of them
slower in one group. The rounds rejected all but one:
`wire_decode/padded/packets_64` at +12%, which read +2% in the next A/A run.
A run whose base cut one case's work by a quarter failed with that case alone
at +35%; its rounds also agreed, at medians of +9.3 to +9.7%, on `stats/tick`
cases whose source was identical on both sides. Separate builds of the same
source can measure up to 12% apart, so the threshold cannot go lower until
that bias is fixed.
`rust-bench` is not calibrated yet: its paired leg outlasts its hour.

The table page shows tonight's comparison above the trend: confirmed
regressions, targets not compared, cases faster in the screen, slowdowns the
Expand All @@ -923,6 +936,11 @@ Each run's evidence directory keeps both builds' estimates (`criterion` and
`paired.json`. The base build streams its diagnostics and retains them in
`base-build.log`, including when it fails.

Each revision builds in its own Cargo target directory (`base` and `current`
under `CARGO_TARGET_DIR`, default `target/bench`). For an A/B comparison, use
`run.sh`'s `BENCH_BASE` mode or a target directory per source tree: two trees
sharing one can leave a bench linked against the other tree's library.

### CodSpeed

The CodSpeed workflow adds instruction counts for every `main` commit that
Expand Down Expand Up @@ -970,3 +988,12 @@ OOM kill does too, even inside a Quest scope: each worker watches `local.py`
through a pidfd and stops once it exits. macOS has no pidfd, so there killing
`local.py` with SIGKILL frees the lock while its workers keep measuring until
their own gates exit, an accepted gap. Stop it with Ctrl-C or SIGTERM instead.

### Python socket sessions

The Python binding runner also sweeps both TCP and track sockets over 1, 4,
and 16 accepting endpoints and 1, 4, and 16 concurrent sessions per endpoint.
Each session sends a checked payload and finishes both halves. The
`socket/<protocol>/endpoints=N/sessions=M` entries report complete sessions per
second, including handshake, FFI calls, reads, and teardown. Run with
`just py bench`; Nightly's binding lane runs the same sweep.
6 changes: 3 additions & 3 deletions bench/browser/page/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async function allocator(connection: Moq.Connection): Promise<Moq.Bandwidth.Hand

/** A reservation on `handle` for a fresh, unannounced track: the allocator sees only its demand. */
function reserve(handle: Moq.Bandwidth.Handle, origin: Moq.Origin.Producer, name: string) {
const publication = origin.createBroadcast(Moq.Path.from(name));
const publication = origin.route(Moq.Path.from(name), { hidden: true });
const track = publication.broadcast.createTrack("video", { priority: 1 });
const reservation = handle.reserve(track.demand(), 2_500_000);
cleanup.push(() => {
Expand Down Expand Up @@ -149,7 +149,7 @@ async function setup(config: Sampling): Promise<void> {
/** Publishes one small-frame track and plays it on every connection. */
async function receive(config: Sampling): Promise<void> {
const publishing = new Moq.Origin.Producer();
const publication = publishing.createBroadcast(Moq.Path.from(`${config.path}/live`));
const publication = publishing.route(Moq.Path.from(`${config.path}/live`), { hidden: true });
const track = publication.broadcast.createTrack("data", { timescale: Moq.Time.Timescale.MILLI });
publication.announce();
const publisher = await Moq.Connection.connect({
Expand Down Expand Up @@ -249,7 +249,7 @@ async function resume(config: Sampling & { cycles: number; idle_ms: number; limi
const waits: number[] = [];
for (let cycle = 0; cycle < config.cycles; cycle++) {
await new Promise((resolve) => setTimeout(resolve, config.idle_ms));
const publication = origin.createBroadcast(Moq.Path.from(`${config.path}/resume/${cycle}`));
const publication = origin.route(Moq.Path.from(`${config.path}/resume/${cycle}`), { hidden: true });
const track = publication.broadcast.createTrack("video", { priority: 1 });
const start = performance.now();
const reservation = handle.reserve(track.demand(), 2_500_000);
Expand Down
2 changes: 1 addition & 1 deletion bench/browser/page/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function publish(config: Transport): Promise<void> {
// Fixture generation stays outside every timed interval.
const fixtures = Array.from({ length: config.frames }, (_, frame) => payload(config.size, frame));
const origin = new Moq.Origin.Producer();
const publication = origin.createBroadcast(Moq.Path.from(config.path));
const publication = origin.route(Moq.Path.from(config.path), { hidden: true });
const producer = publication.broadcast;
// Retention covers the whole run, so a slow consumer cannot lose a group to eviction.
const track = producer.createTrack("data", { timescale: Moq.Time.Timescale.MILLI, maxAge: Milli(600_000) });
Expand Down
3 changes: 2 additions & 1 deletion bench/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ def criterion_baseline(directory, discovery, benchmark):


# A paired case beyond this change is flagged, and fails the nightly only when its rounds agree.
# Loose until A/A runs on the hosted runner measure its noise floor.
# Separate builds of unchanged code measured up to 12% apart in A/A runs, so lowering this
# waits on fixing that bias (quest/m1/bench-ci.md).
THRESHOLD_PCT = 10
# Each alternating round costs seconds per case, so a noisy night confirms only its worst slowdowns.
CONFIRMATIONS = 20
Expand Down
Loading
Loading