Skip to content

refactor(net)!: drop moq-net's direct tokio dependency#2377

Merged
kixelated merged 7 commits into
devfrom
claude/remove-tokio-moq-net-c90873
Jul 18, 2026
Merged

refactor(net)!: drop moq-net's direct tokio dependency#2377
kixelated merged 7 commits into
devfrom
claude/remove-tokio-moq-net-c90873

Conversation

@kixelated

@kixelated kixelated commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Continues the tokio removal that started with the caller-driven (Session, Driver) refactor (#2302). This pass removes every direct tokio use from moq-net, so the crate's only remaining runtime tie is timers via web_async::time (tokio-backed on native, wasmtimer on wasm). On the wasm target, moq-net's dependency tree now contains no tokio at all.

  • All 28 tokio::select! sites are now explicit kio::wait poll closures that call the model's poll_* methods directly (poll_requested_track, poll_next, poll_unused, poll_next_frame, poll_read_chunk, ...), pinning a future via waiter.poll_future only for compound work or transport-trait methods that have no poll form (Session::closed, Reader::decode, timers). New poll_closed methods on track::Producer, group::Producer, and broadcast::Dynamic fill the gaps, with the async closed() now wrapping the poll per the usual plumbing. The one shared helper that remains is util::err_only, which parks a fallible future on Ok (replacing the Err(e) = fut select pattern-guard arms in the session drivers).
  • tokio::sync::watch channels became kio::Producer/Consumer<u8> with a last-observed value on the reader (lite::priority, the publisher's track-priority fan-out). send_if_modified maps to compare-before-write so unchanged values still don't wake.
  • tokio::sync::Notify in the ietf Control (MAX_REQUEST_ID flow control) and the watch-based PeerSetup slot became kio::Shared state.
  • The ietf control-stream adapter's mpsc channels and async Mutex became a small Queue over kio::Shared (the reverse-queue pattern from its docs): virtual streams push, the writer task drains, and a single-owner QueueWriter's drop FINs the matching recv stream exactly like the old sender-drop. accept_bi callers 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! became std::pin::pin!; tokio moves to dev-dependencies (tests still use #[tokio::test] and paused time), and the unused io-util feature is gone.
  • Biasing note: tokio::select! randomizes arm order; the poll closures check in declaration order. The order matches the previous biased; sites and puts closure/error watchdogs first elsewhere, which is deterministic and at worst equivalent.

Public API changes

  • Breaking: removed impl From<tokio::time::Instant> for moq_net::Timestamp. It existed only because web_async::time::Instant is tokio's Instant on native; From<std::time::Instant> remains. Hence the dev target.
  • Additive: poll_closed on track::Producer, group::Producer, and broadcast::Dynamic (public poll counterparts of the existing closed()).
  • Everything else touched is private or pub(crate) (PeerSetup, Control, PriorityHandle, the adapter internals, util).

Cross-package sync

No wire change (no draft update) and no js/net mirror needed: the JS side has no tokio equivalent, and no message, field, or negotiation changed. doc/concept doesn't document the runtime requirement; the crate-level Async docs in lib.rs and the rs/CLAUDE.md poll-plumbing note were updated instead.

Test plan

  • just rs check (fmt, clippy -D warnings, all tests) is green; cargo test -p moq-net passes 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-net is clean.
  • cargo check --workspace --all-targets confirms no dependent crate used the removed From impl.
  • The wasm target (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

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>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @kixelated, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

kixelated and others added 6 commits July 17, 2026 14:56
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
kixelated enabled auto-merge (squash) July 18, 2026 01:58
@kixelated
kixelated merged commit 59eab37 into dev Jul 18, 2026
3 checks passed
@kixelated
kixelated deleted the claude/remove-tokio-moq-net-c90873 branch July 18, 2026 02:18
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant