refactor(net)!: drop moq-net's direct tokio dependency#2377
Merged
Conversation
Replace every direct tokio use in moq-net with the crate's own kio/futures plumbing: tokio::select! sites become kio::wait poll closures (plus small race2/race3/err_only helpers in util), tokio::sync::watch/Notify become kio::Producer/Consumer and kio::Shared, and the ietf control-stream adapter moves to futures::channel::mpsc + futures::lock::Mutex. The public From<tokio::time::Instant> impl on Timestamp is removed (breaking), and tokio moves to dev-dependencies (tests only). Timers still go through web_async::time, which is tokio-backed on native, so driving a session still needs a tokio runtime there; the model layer no longer does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @kixelated, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Replace the futures mpsc channels and async Mutex in the control-stream adapter with a small Queue over kio::Shared: writers push, the reader drains, and closure is a plain flag (QueueWriter drops close, mirroring the old sender-drop FIN). accept_bi callers now park on the shared queue directly, so the async Mutex around the receiver disappears, and the adapter builds its own control queue instead of having the session thread a channel through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the race2/race3 helpers for explicit kio::wait closures that call the model's poll_* methods directly (poll_requested_track, poll_next, poll_unused, poll_next_frame, poll_read_chunk, poll_pop), pinning a future only for compound or transport-trait work with no poll form. Adds the missing poll_closed to track::Producer, group::Producer, and broadcast::Dynamic, with the async closed() now wrapping it per the usual plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…utures Where a raced operation is one of our own types, call a poll method instead of pinning the async form: TaskSet::poll drives the task set in the session drivers, broadcast::Dynamic::poll_closed replaces the pinned closed() in the serve loop, recv_next becomes poll_recv_next, and serve_step takes a poll closure (poll_next_frame / poll_read_chunk) instead of a work future. Pinned futures remain only for transport-trait methods, timers, and compound child loops that have no poll form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The main-into-dev merge (fe9b782) left a double blank line in error.rs, failing cargo fmt --check for every PR targeting dev. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings on the kio Queue rewrite, restoring behavior the old tokio mpsc gave for free: - Close the control and incoming queues when adapter.run() exits, and make Queue::push report closure so virtual writes fail with Error::Closed instead of buffering forever into a queue nobody drains (zombie session after a clean control-stream FIN). - Close a VirtualRecvStream's queue on drop so late routed follow-ups are discarded, like a send to a dropped mpsc receiver, instead of accumulating while the streams-map entry lingers. - Poll the SUBSCRIBE_UPDATE/FIN reader before the group backlog in run_track, and the native accept before the virtual queue in draft-16 accept_bi. The old unbiased select! serviced the rare arm eventually; a fixed order must put the rare, starvable arm first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kixelated
enabled auto-merge (squash)
July 18, 2026 01:58
kixelated
added a commit
that referenced
this pull request
Jul 18, 2026
Ports the route machinery to the kio::wait idiom from #2377: the announce loop's route watch, the serve loop's event race, the TRACK_INFO and SUBSCRIBE_OK decodes, the assignment drive, and the origin linger timer all poll under one waiter instead of tokio::select!. Assignments::next becomes test-only now that the serve loop polls directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Continues the tokio removal that started with the caller-driven
(Session, Driver)refactor (#2302). This pass removes every direct tokio use frommoq-net, so the crate's only remaining runtime tie is timers viaweb_async::time(tokio-backed on native,wasmtimeron wasm). On the wasm target,moq-net's dependency tree now contains no tokio at all.tokio::select!sites are now explicitkio::waitpoll closures that call the model'spoll_*methods directly (poll_requested_track,poll_next,poll_unused,poll_next_frame,poll_read_chunk, ...), pinning a future viawaiter.poll_futureonly for compound work or transport-trait methods that have no poll form (Session::closed,Reader::decode, timers). Newpoll_closedmethods ontrack::Producer,group::Producer, andbroadcast::Dynamicfill the gaps, with the asyncclosed()now wrapping the poll per the usual plumbing. The one shared helper that remains isutil::err_only, which parks a fallible future onOk(replacing theErr(e) = futselect pattern-guard arms in the session drivers).tokio::sync::watchchannels becamekio::Producer/Consumer<u8>with a last-observed value on the reader (lite::priority, the publisher's track-priority fan-out).send_if_modifiedmaps to compare-before-write so unchanged values still don't wake.tokio::sync::Notifyin the ietfControl(MAX_REQUEST_ID flow control) and thewatch-basedPeerSetupslot becamekio::Sharedstate.Mutexbecame a smallQueueoverkio::Shared(the reverse-queue pattern from its docs): virtual streams push, the writer task drains, and a single-ownerQueueWriter's drop FINs the matching recv stream exactly like the old sender-drop.accept_bicallers park on the shared queue directly, which eliminates the async mutex around the receiver, and the adapter owns its control queue instead of having the session thread a channel through.tokio::pin!becamestd::pin::pin!; tokio moves to dev-dependencies (tests still use#[tokio::test]and paused time), and the unusedio-utilfeature is gone.tokio::select!randomizes arm order; the poll closures check in declaration order. The order matches the previousbiased;sites and puts closure/error watchdogs first elsewhere, which is deterministic and at worst equivalent.Public API changes
impl From<tokio::time::Instant> for moq_net::Timestamp. It existed only becauseweb_async::time::Instantis tokio'sInstanton native;From<std::time::Instant>remains. Hence thedevtarget.poll_closedontrack::Producer,group::Producer, andbroadcast::Dynamic(public poll counterparts of the existingclosed()).pub(crate)(PeerSetup,Control,PriorityHandle, the adapter internals,util).Cross-package sync
No wire change (no draft update) and no
js/netmirror needed: the JS side has no tokio equivalent, and no message, field, or negotiation changed.doc/conceptdoesn't document the runtime requirement; the crate-level Async docs inlib.rsand thers/CLAUDE.mdpoll-plumbing note were updated instead.Test plan
just rs check(fmt, clippy-D warnings, all tests) is green;cargo test -p moq-netpasses all 492 unit tests, including the priority-queue notification and virtual-stream tests that exercise the kio replacements across tasks.RUSTDOCFLAGS="-D warnings" cargo doc -p moq-netis clean.cargo check --workspace --all-targetsconfirms no dependent crate used the removedFromimpl.cargo check -p moq-wasm --target wasm32-unknown-unknown) fails identically on the base commit with a pre-existing local-toolchain error, so it gates on CI's nix build as usual.🤖 Generated with Claude Code