Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
136655c
chore(quest): claim quest/m1/poll-first/async-feature.md
kixelated Nov 2, 2026
c9397e9
feat(json)!: generate async wrappers behind an opt-in feature
kixelated Nov 2, 2026
b5a2365
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 4, 2026
731a564
refactor(net): generate the remaining async wrappers
kixelated Nov 4, 2026
924268f
refactor(net): drive the origin front as a poll machine
kixelated Nov 4, 2026
f693900
Merge the origin front poll machine into the async-feature branch
kixelated Nov 4, 2026
21bd4d1
feat(room,stats,crypto,auth)!: generate async wrappers behind an opt-…
kixelated Nov 4, 2026
58688db
refactor(tokio,uring,c,audio,transcode): generate one-poll async wrap…
kixelated Nov 4, 2026
2401c72
feat(mux)!: generate async wrappers behind an opt-in feature in hang,…
kixelated Nov 4, 2026
c12f03a
Merge the remaining sibling libraries into the async-feature branch
kixelated Nov 4, 2026
85fe0b0
Merge hang, moq-mux, and moq-hls into the async-feature branch
kixelated Nov 4, 2026
6b52972
feat(net)!: turn the async feature off by default
kixelated Nov 4, 2026
e09d041
quest: finish async-feature; the epic holds the remaining async logic
kixelated Nov 5, 2026
978289a
refactor(net): give the server handshake a poll form
kixelated Nov 5, 2026
f5eecc2
Merge the server handshake poll form into the async-feature branch
kixelated Nov 5, 2026
cdf5197
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 5, 2026
3f073b0
refactor(hls): generate the contributions rendition wrapper
kixelated Nov 5, 2026
5868d58
fix(net): gate the pacing window with its async-only caller
kixelated Nov 5, 2026
3156120
fix(crypto): scan the poll-only build with its provider feature
kixelated Nov 5, 2026
51e901e
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 5, 2026
ab41663
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 5, 2026
ef5e9ef
refactor(net): submit poll tasks to the task set
kixelated Nov 5, 2026
31c2294
Merge commit 'ef5e9ef0d7' into worktree-agent-a71eb43c9590c7850
kixelated Nov 5, 2026
a15bdad
Merge commit 'ef5e9ef0d7' into worktree-agent-a8ce0ffcc4c8f5e19
kixelated Nov 5, 2026
4f9a1e5
Merge commit 'ef5e9ef0d7' into worktree-agent-a6d6d332cf0a98ed1
kixelated Nov 5, 2026
70ebe47
Merge commit 'ef5e9ef0d7' into worktree-agent-ad5d7c4badde0308a
kixelated Nov 5, 2026
538897b
Merge commit 'ef5e9ef0d7' into worktree-agent-af29127ea73be902a
kixelated Nov 5, 2026
72c8409
Merge commit 'ef5e9ef0d7' into worktree-agent-a5b5967551fe77175
kixelated Nov 5, 2026
caeb6dc
Merge commit 'ef5e9ef0d7' into worktree-agent-a23522ba69e572ced
kixelated Nov 5, 2026
554647b
refactor(mux): poll the source subscriptions and fMP4 group read
kixelated Nov 5, 2026
55f2971
refactor(net): poll the client and server handshakes, session and aut…
kixelated Nov 5, 2026
796e8a3
refactor(net): poll the IETF fetch object writers
kixelated Nov 5, 2026
4210012
chore(net): keep formatting outside the publisher slice untouched
kixelated Nov 5, 2026
fbb738f
refactor(net): poll the IETF auth present and serve, and request-stre…
kixelated Nov 5, 2026
f008857
refactor(net): poll the IETF FETCH preparation and read
kixelated Nov 5, 2026
3e0a929
refactor(auth): poll the lease clock and report waits; poll catalog a…
kixelated Nov 5, 2026
1acbc70
refactor(net): poll the IETF namespace requests and PUBLISH_NAMESPACE…
kixelated Nov 5, 2026
d381cbb
refactor(net): hold the server answer in fields; note the new model p…
kixelated Nov 5, 2026
3a576d1
refactor(net): poll the IETF fetch and track status serves
kixelated Nov 5, 2026
1a597e6
refactor(net): drop an announce's body before its route, as until did
kixelated Nov 5, 2026
b095f51
refactor(hls): poll the export readiness waits, catalog watcher, and …
kixelated Nov 5, 2026
1f992e0
refactor(net): poll the IETF track listings, held subscriptions, and …
kixelated Nov 5, 2026
c2d364e
refactor(net): poll the IETF namespace advertisement loops
kixelated Nov 5, 2026
4e4ffb3
refactor(net): poll the IETF SUBSCRIBE serve, fills, and request disp…
kixelated Nov 5, 2026
bc91976
refactor(net): keep base formatting outside the slice; expose ServePu…
kixelated Nov 5, 2026
5bbdca1
refactor(hls): poll the segment fetch, init load, playlist render, an…
kixelated Nov 5, 2026
5bb824d
refactor(net): write the IETF fill and FETCH preparation as kio machines
kixelated Nov 5, 2026
2fea94b
refactor(net): poll the IETF subscriber's second half
kixelated Nov 5, 2026
29c5d31
refactor(hls): poll the media watcher as a driver task
kixelated Nov 5, 2026
fd68b1f
refactor(hls): poll the server's instance follower; note the new polls
kixelated Nov 5, 2026
ea2c2a9
refactor(net): keep the IETF receive guards' drop order
kixelated Nov 5, 2026
5eb2182
docs(hls): name the poll tasks in comments
kixelated Nov 5, 2026
0f46c9f
Merge branch 'worktree-agent-a23522ba69e572ced' into refactor/convert…
kixelated Nov 5, 2026
587f4d1
Merge branch 'worktree-agent-af29127ea73be902a' into refactor/convert…
kixelated Nov 5, 2026
2bacea2
Merge branch 'worktree-agent-a6d6d332cf0a98ed1' into refactor/convert…
kixelated Nov 5, 2026
e2c28ce
Merge branch 'worktree-agent-ad5d7c4badde0308a' into refactor/convert…
kixelated Nov 5, 2026
25eb9fd
Merge branch 'worktree-agent-a71eb43c9590c7850' into refactor/convert…
kixelated Nov 5, 2026
e9bca37
Merge the IETF publisher's second half into the poll conversion
kixelated Nov 5, 2026
c5dde45
refactor(net): dispatch IETF bidi requests as poll tasks
kixelated Nov 5, 2026
07e3eb1
refactor(net): poll the IETF fill write, joining FETCH, alias wait, a…
kixelated Nov 5, 2026
caf3197
refactor(net): keep the IETF serve futures for tests only
kixelated Nov 5, 2026
15222fe
feat(net): give track::Ordered a poll_finished
kixelated Nov 5, 2026
eb3f8d4
refactor(net): poll the IETF session driver
kixelated Nov 5, 2026
0f96837
docs(net): name the IETF race's drop order
kixelated Nov 5, 2026
68ae1dd
Merge branch 'worktree-agent-a58ea9549c8251062' into refactor/convert…
kixelated Nov 5, 2026
87b7682
refactor(net): drop the future adapters and keep the writer's async h…
kixelated Nov 5, 2026
fe88e03
style(net): format the poll conversion
kixelated Nov 5, 2026
9a3f2c2
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 5, 2026
5f52d26
quest: the poll-first epic keeps only moq-hls's import driver
kixelated Nov 5, 2026
fe0da30
test(drill): retarget the max-age mutation at the SUBSCRIBE serve mac…
kixelated Nov 5, 2026
47c7a8b
fix: keep the poll-only feature matrix green
kixelated Nov 5, 2026
f043156
Merge origin/main into quest/m1/poll-first/async-feature
kixelated Nov 5, 2026
cabf5bc
quest: moq-hls's import driver stays async; poll-first is done
kixelated Nov 5, 2026
1b399b9
Merge remote-tracking branch 'origin/main' into HEAD
kixelated Nov 5, 2026
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
7 changes: 7 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions doc/lib/rs/hang.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ carries a timestamp with each payload.
cargo add hang
```

The `poll_*` methods work by default. Enable the `async` Cargo feature to await
`Catalog::subscribe` and the catalog consumer's `next()` instead.

hang is the format, not the pipeline. [`moq-mux`](/lib/rs/moq-mux) produces the
catalog for you and decodes any container in group order
(`moq_mux::container::Consumer`). Its `Delay { min, max }` says how far behind
Expand Down
3 changes: 3 additions & 0 deletions doc/lib/rs/moq-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ stream are the catalog modes on the [hang](/concept/hang#data-tracks) page.
Window is a third framing for a raw track: both ends opt into it, and a
generic catalog reader will not pick it up.

The `poll_*` methods work by default. Enable the `async` Cargo feature to await
`next()`, `latest()`, or `refresh()` instead.

- **snapshot**: lossy latest value, with RFC 7396 merge-patch deltas. An unchanged update publishes nothing. Several owners can edit their own keys in place instead of clobbering one document. Deltas ride one group until a joiner wants a fresh one: set the track's `refresh` ratio (8 is typical) and drive `Producer::refresh`, which restates the value in a new group. Without a ratio a group rolls only at its caps.
- **stream**: lossless append-log in a single group. A reader that falls behind fails the read rather than resuming mid-log.
- **window**: a bounded run of records a reader can join at any point. A new group restates what it keeps explicitly, so a reader that was keeping up is not handed a record twice. Its ops roll on the track's `refresh` ratio the same way.
Expand Down
4 changes: 4 additions & 0 deletions doc/lib/rs/moq-mux.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,9 @@ only when they share that clock.
cargo add moq-mux
```

The `poll_*` methods work by default. Enable the `async` Cargo feature to await
the exporters' `next()`, a container's `read()`, and the async constructors such
as `catalog::Consumer::new` and `Source::catalog` instead.

API: [docs.rs/moq-mux](https://docs.rs/moq-mux). The commands that exercise it
are [`moq-cli`](/bin/cli).
5 changes: 3 additions & 2 deletions doc/lib/rs/moq-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ What you use it for, beyond what the concept page already describes:
- **Authored tracks default to a zero `max_age`.** `track::Info::default()` sets `max_age` to `Some(Duration::ZERO)`, the live edge only; a publisher that wants readers to reach history sets a longer bound, or `with_max_age(None)` for no publisher limit. Media tracks from `hang::container::track_info` declare `hang::container::MAX_AGE` (30 seconds). `max_age` is how long a superseded group stays fresh, never the newest group; it frees no bytes, which the cache pool's expiry reclaims. Received metadata keeps absence as no limit, and an origin's `cache_duration` can only cap it lower.
- **End content by finishing tracks.** A broadcast has no finished state. When the input ends, finish each track; the broadcast stays announced and keeps serving them until you close it or drop its last producer. How long to keep it up for late readers is yours to decide: `moq_mux::Linger` holds a hang broadcast for a fixed window.
- **Demand says whether anyone is watching.** `demand()` on a track, group, or broadcast says whether a subscriber is attached. First-party capture does not wait for it: video encodes while enabled and audio keeps raw PCM to encode when someone listens, so a late subscription's `delay.max` or a FETCH finds the recent past. Demand gates bandwidth reservations, transcode rungs, and on-demand video where a publisher opts in, which saves the encode but leaves nothing from before the first subscriber. Holding a broadcast or track consumer is not demand: a track consumer requests nothing until it queries, subscribes, or fetches, and those count until dropped. A shared fetch stays up until its last reader leaves. Demand rolls up: a frame being read keeps its group wanted, a group a subscription or fetch handed out keeps its track wanted until read to its end, and any wanted track keeps its broadcast wanted.
- **Use the model without a runtime.** Disable default features on `moq-net` to use its `poll_*` APIs and origin driver with caller-supplied time. The default `async` feature adds async helpers, `time::run`, and both wire sessions.
- **Use the model without a runtime.** By default `moq-net` is its `poll_*` APIs and origin driver with caller-supplied time. The `async` feature adds their async wrappers, `time::run`, and both wire sessions.
- **You drive the session, or `moq-tokio` does.** `connect` and `accept` return a session plus a driver that never reads the clock itself. `moq_net::time::run` polls it on tokio. `moq-tokio` hides that. A custom transport implements `moq_net::transport::poll`, and `moq_net::transport::memory::pair` links two sessions in one process with no sockets, for tests or in-process links.
- **A session bounds what its peer can hold.** Each request holds a stream, so the transport's stream credit bounds subscriptions and FETCHes. `session::Limits` (via `Client::with_limits` and `Server::with_limits`) bounds the announcements per session. The default suits a relay mesh, so lower it for untrusted peers. Past it, the session closes with `TOO_MANY_REQUESTS`. Peer-declared lengths are capped before they are buffered.
- **Authorization is in band.** On moq-lite 07 (`moq-lite-07-wip`, opt-in) and on moq-transport draft-18+ with the [MoQ Auth extension](/draft/moq-auth), `session.auth()` presents tokens without reconnecting and watches the grant the peer sent back. `add(token)` resolves with the token and how the peer took it: `Answer::Granted` on its AUTH stream, or `Answer::Requests` on moq-transport without AUTH, where only a request that names it carries it. A handle passed to `Client::with_auth` outlives each session, so its tokens, and the routes naming them, carry across your own reconnects. A server verifies tokens itself by taking `handshake.requests()` before `ok()`. `authorize(&grant)` narrows or widens a live session on any version: what falls outside resets with `Unauthorized` and the session stays up. A client that publishes outside its grant closes the session, naming the path.
- **A graceful close waits.** `session.close().await` withdraws announcements and drains for up to one second. `moq-lite-05` and newer wait for the subscriber to receive finished tails; older Lite versions and IETF wait only for transport ACKs. `abort` ends immediately.

```bash
cargo add moq-net moq-tokio
cargo add moq-net --features async
cargo add moq-tokio
```

See the [Rust quick start](/lib/rs/#quick-start) and
Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ their end report, so an embedder's drain alert pages on real cuts.
- [Catalog contributions become includes on the root's timeline](/quest/m1/catalog-include.md) - rename the unreleased `contributions` map before the cut and state that included broadcasts share the root's timeline
- [Unbounded groups](/quest/m1/unbounded-groups/README.md) - groups and frames lose their size caps; a group keeps a 32 MB tail, and readers behind it replay through paced flights or refresh
- [Session policy lives in moq-net](/quest/m1/session-policy/README.md) - accept and connect deadlines, admission, window widening and reconnect live once in moq-net; moq-tokio and moq-relay's io_uring path call them
- [Poll-first async](/quest/m1/poll-first/README.md) - library crates compile no coroutines by default: async wrappers are generated from `poll_*` and gated behind an `async` feature
- [moq auth serve scopes each client certificate by its chain](/quest/m1/auth-cert-rules.md) - per-certificate and per-intermediate rules in a reloaded TOML config, keyed by the chain hashes the relay sends
- [A replacement that fails before delivering never ends a live track](/quest/m1/route-swap-keeps-incumbent.md) - a route change whose replacement aborts before its first group leaves readers on the source that was serving
- [A Rust relay never replays from group 0 to a lite-03/04 subscriber](/quest/m1/relay-inflight-replay.md) - A Rust relay or publisher serving a lite-03 or lite-04 SUBSCRIBE from a track copy that is still resolving starts at the newest group once the copy resolves
Expand Down
77 changes: 0 additions & 77 deletions quest/m1/poll-first/README.md

This file was deleted.

68 changes: 0 additions & 68 deletions quest/m1/poll-first/async-feature.md

This file was deleted.

4 changes: 0 additions & 4 deletions quest/m1/session-policy/accept.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,3 @@ see it for the decisions and research.

Public API: breaking in moq-net (`Server`, `Handshake`, the transport session
trait), with MIGRATE notes. Wire: none.

## Required

- [Generated async wrappers](/quest/m1/poll-first/async-feature.md) - gives the handshake a poll form these machines wait on
12 changes: 11 additions & 1 deletion rs/hang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ rust-version.workspace = true
keywords = ["quic", "http3", "webtransport", "media", "live"]
categories = ["multimedia", "network-programming", "web-programming"]

[package.metadata.docs.rs]
features = ["async"]

[features]
default = []
# Generated async wrappers for the poll methods, and `Catalog::subscribe`. Enable where you `.await` them.
async = ["kio/async"]

[dependencies]
bytes = { workspace = true }
hex = { workspace = true }
Expand All @@ -30,7 +38,9 @@ features = ["from", "display", "debug"]

[dev-dependencies]
anyhow = { workspace = true }
moq-mux = { path = "../moq-mux" }
# Compile the async API used by the tests.
hang = { path = ".", features = ["async"] }
moq-mux = { path = "../moq-mux", features = ["async"] }
moq-tokio = { path = "../moq-tokio", default-features = false, features = ["quic", "websocket"] }
tokio = { workspace = true, features = ["macros", "fs"] }
tracing = { workspace = true }
Expand Down
8 changes: 8 additions & 0 deletions rs/hang/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ How to update your code for each breaking change, newest first.

## Unreleased

### The async API is opt-in

Enable the `async` Cargo feature to keep awaiting `catalog::Consumer::next` and `Catalog::subscribe`:
`hang = { version = "...", features = ["async"] }`. This also applies with `default-features = false`.
The feature is off by default; `catalog::Consumer::poll_next` stays available without it.
The generated `next` no longer requires `Catalog<E>: Unpin`.
The catalog format and JS and binding APIs are unchanged.

### The catalog track advertises a zero max age ([#7729](https://github.com/moq-dev/moq/pull/7729))

`Catalog::default_track_info` takes moq-net's new zero default, so the catalog track advertises the live edge only where it advertised no limit. A late joiner still reads the newest snapshot, which never goes stale.
Expand Down
Loading
Loading