Skip to content

feat(sock): extract the shared listener plumbing and steer the uring endpoint - #3078

Merged
kixelated merged 4 commits into
devfrom
uring-m4-shard
Aug 27, 2026
Merged

feat(sock): extract the shared listener plumbing and steer the uring endpoint#3078
kixelated merged 4 commits into
devfrom
uring-m4-shard

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

M4 PR 2 on #2875, stacked on #3073: the shared-crate extraction the epic's M2/M4 text committed to ("extract the runtime-neutral ... CID codec, eBPF loader, and socket configuration that remain private in moq-tokio"), validated by its second consumer in the same diff.

moq-sock (new crate, 0.1.0) owns the thread-per-core listener plumbing both runtimes need:

  • bind moves out of moq-tokio wholesale (dual-stack UDP/TCP, grown socket buffers, SO_REUSEPORT) and is re-exported as moq_tokio::bind, so the external callers in moq-cli and moq-relay keep their paths.
  • steer becomes moq_sock::shard: Shard (absorbed from listen.rs, now with its group-discipline invariants documented on the type since it is publicly mintable), the in-order group bind with its port Lock and bind probe, the classic-BPF steering filter, and cid_prefix. The kernel-facing behavior is untouched; the module's tests moved with it.
  • cpu owns core pinning (cores, pin), a thin re-export-typed wrapper over core_affinity.

moq-tokio's worker::Workers forms its group from the shared crate with no behavior change; it drops its socket2 and core_affinity dependencies.

moq-uring is the second consumer: endpoint::Config::shard makes every connection id the endpoint issues (initial accepts, rotations for migration, and dials alike) lead with the slot's steering byte. Dials carrying it is the cluster shape: a worker's upstream dial gets its responses steered back to that worker.

Validation

tests/workers.rs runs a steered two-worker reuseport group end to end on real threads: sockets bound in index order on the main thread, one Worker + Endpoint per thread, 16 dials against the shared port. The steering assertion is structural: post-Initial packets (handshake continuation included) select by the id's first byte, so a wrong prefix strands the handshake on the wrong worker and the dial times out; every dial completing plus both members being fed is the proof. Passes in ~100ms on this kernel (7.1.3), kernel-gated with the loud-skip pattern like the rest.

just check and just test pass across the affected crates (moq-tokio's worker/steer suites included).

Notes for review

  • moq-sock is publishable (release-plz will pick it up at 0.1.0) because moq-tokio, a published crate, now depends on it.
  • No /doc page: moq-sock is internal plumbing with no end-user surface, like kio. The crate map entry in rs/CLAUDE.md and its README carry the documentation.
  • Shard::new becoming public softens moq-tokio's old "cannot be minted outside the crate" guarantee; the invariant (bind once, in index order, never resize) now lives in the type's docs and each runtime's group owner. The uring group owner that enforces it structurally comes with the relay-integration PR.

(Written by Claude Fable 5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01XLJU7rEfPe7hn7K5jX77mb

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04b2989cb3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-sock/src/shard.rs
Base automatically changed from uring-m4 to dev August 26, 2026 22:30
…endpoint

moq-sock is the runtime-neutral crate the M4 plan called for: bind (dual
stack UDP/TCP, grown buffers, SO_REUSEPORT) moves out of moq-tokio wholesale
and is re-exported as moq_tokio::bind, and steer becomes moq_sock::shard
(Shard, the group bind with its port lock and probe, the cBPF steering
filter, cid_prefix), with cpu owning core pinning. moq-tokio's workers now
form their group from the shared crate, unchanged in behavior.

moq-uring is the second consumer: endpoint::Config::shard makes every
connection id the endpoint issues (accepts, rotations, and dials alike) lead
with the slot's steering byte, so a steered reuseport group of uring workers
keeps each connection on the worker that owns it. tests/workers.rs runs a
two-worker group end to end: handshakes only complete if the steering holds,
and both members must be fed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLJU7rEfPe7hn7K5jX77mb

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6549ec20d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-sock/src/shard.rs
/// kernel identifies a member by its position in the group, which is the order
/// the sockets bound. An unsharded bind is the plain one.
pub(crate) fn bind(addr: SocketAddr, shard: Option<Shard>) -> io::Result<UdpSocket> {
pub fn bind(addr: SocketAddr, shard: Option<Shard>) -> io::Result<UdpSocket> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make shard group construction hold the port lock

When two same-UID processes concurrently construct the same fixed-port group using this public bind loop, both index-zero probes can finish before either reuseport bind, after which their sockets silently join one interleaved group. Because bind neither acquires nor requires the separate Lock, every call can return Ok while the positional BPF filter routes one process's connection IDs to the other process's sockets during a rolling restart. Make group construction acquire and retain the exclusion handle, or require an owned token, so this invalid sequence cannot be represented. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L157-L162

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed as a real gap in the API's shape, and deliberately deferred to #3092 rather than fixed here.

Not a live bug: both callers (moq_tokio::worker::Workers::new, moq_relay::uring::Workers::bind) acquire the lock before the first member binds. What this PR changed is that the invariant used to be crate-private and is now prose an outside caller cannot be held to, so daa3cb2 at least promotes the precondition from a comment inside the function body onto bind's rendered docs.

The fix you describe (an owned token) is bigger than it looks: moq-tokio never calls shard::bind from its construction loop, it calls Worker::spawn, and the bind happens on the worker thread inside the quinn and noq backends. A Group handing out sockets means moving where moq-tokio creates its sockets across both backends. #3092 carries that, and moq-sock is at 0.0.1 and unpublished, so the reshape stays cheap.

(Written by Claude Opus 5)

Comment thread rs/moq-uring/tests/workers.rs Outdated
kixelated and others added 2 commits August 26, 2026 16:14
`cid_prefix` spends `256 / count` values of the first connection-id byte,
so a group larger than `MAX_SHARDS` leaves it a zero-width stride and
`random_range(0..0)` panics. The bound used to be held by the one caller
that could mint a shard; now that `Shard` is public and `Endpoint::with_shard`
takes one, `Shard::new(0, 257)` returns `Some` and panics downstream.

Bound it in the constructor, so every shard that exists is one the filter
can steer. The count checks in `moq_tokio::worker::Workers::new` and the
relay's uring group stay: they turn the same mistake into an error that
names the limit, rather than a `None` the caller has to interpret.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The crate is not going to crates.io for a while, and 0.0.1 says so: the
shape here is still free to move, which matters because it is currently
three prose invariants (hold the lock, bind in index order, never resize)
rather than a type that enforces them.

`bind`'s doc now carries the lock precondition. The reasoning was already
written down, but inside the function body, where nobody reading the
rendered docs would find it: the probe only refuses a group that is
already bound, so two groups constructing at once each pass it and then
interleave. Both callers in this repo acquire the lock; an outside caller
had no way to know it had to.

`DIALS` goes to 32. Steering cannot be asserted deterministically from the
test, since the client picks the connection id the filter hashes, so the
count is the only lever: 16 dials left a `2^-15` chance of every Initial
landing on one worker and failing a correct implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: daa3cb289b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-sock/src/cpu.rs Outdated
`CoreId` was an alias for `core_affinity::CoreId`, so both `cores` and
`pin` handed that crate's type to callers. moq-sock is socket and listener
plumbing, not a wrapper for that dependency, and the exemption for a thin
wrapper does not stretch to a whole crate: as written, an upstream type
change is a breaking moq-sock change nobody here chose.

Make it an opaque newtype. Callers only ever take one from `cores` and
hand it back to `pin`, so the only thing they needed from the inside was
the number for a log line, which `id()` still gives them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kixelated
kixelated merged commit 4630391 into dev Aug 27, 2026
5 checks passed
@kixelated
kixelated deleted the uring-m4-shard branch August 27, 2026 07:43
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