Skip to content

quest(m1): sans-IO compared on the lite upstream SUBSCRIBE - #7058

Merged
kixelated merged 5 commits into
mainfrom
quest/m1/poll-first/sansio-compare
Oct 28, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m1/poll-first/sansio-compare

Conversation

@kixelated

@kixelated kixelated commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Spike for quest/m1/poll-first/sansio-compare.md. It converts one moq-net protocol path, the lite subscriber's upstream SUBSCRIBE control stream, into a sans-IO core driven by the existing kio serve loop, then compares the two styles on effort, testability, rust2's TypeScript translation and FFI export, compile time, and runtime. This PR deletes the quest and records the findings and the decision below in the poll-first epic's Plan.

The prototype is not for merge. It lives on spike/sansio-upstream (c3f66f8398), off main at 4018a32198.

The prototype

rs/moq-net/src/lite/upstream.rs adds Upstream, the subscriber's wire state for one SUBSCRIBE, in quinn-proto/rustls style. It does no I/O, holds no kio::Waiter, and reads no clock:

  • inputs: new(version, Subscribe, start), update(Params) -> Updated, want(group), handle(SubscribeResponse) -> Answer, handle_fin(requested_end) -> Fin;
  • outputs: poll_transmit() -> Option<Transmit> (SUBSCRIBE, then one coalesced SUBSCRIBE_UPDATE per change), and the Answer / Fin / Updated values the driver applies to the track.

It took over every wire decision the serve loop made inline: the pre-lite-05 SUBSCRIBE_OK gate, when a SUBSCRIBE_START still applies (start == requested), the lite-05 lenient END, DROP ranges and codes, bounded-FIN "covered" versus track FIN versus protocol violation, and the owed/covered group arithmetic. lite::subscriber's Establish and ServeLoop now open the stream, buffer what the core transmits, decode responses into it, and apply its answers to track::Producer and the Tail ledger.

The seam is the message, not the byte. The codec is already pure (Encode/Decode over buffers), and coding::Reader leases bytes from the transport so flow control counts unconsumed bytes. A bytes-in core would have to copy them or redo that lease, so the core takes decoded messages and returns typed ones. Writing through Writer::buffer also keeps the per-message control stats.

What could not move: anything that waits or reads model state. That covers the route-replacement check on an unanswered SUBSCRIBE (a kio wait on the source's validity), the session-closed watch, the linger deadline, the Settle grace, the fetch servers, and our own FIN/abort. These are tied to ServeEnd teardown. Where a wire rule needs model state, it crosses the boundary as data. Answer::End { strict } lets the driver decide after finish_at fails, and handle_fin takes the demand's requested end.

Behavior: unchanged, except that SUBSCRIBE_UPDATEs buffered in one poll turn coalesce into one. Each update restates every parameter, so nothing is lost. All 3126 moq-net tests pass on the branch, including 10 new core tests.

Results

kio poll_* (main) sans-IO core + kio driver (spike)
Effort n/a Core: 329 lib + 266 test lines. Driver: −292/+169 lib lines. Net +206 lib lines for one path. Every model touch became a variant: 5 Answer, 2 Fin, 2 Updated fields. 2 #[cfg] gates lifted (coding::codes, has_drop_code) so the core builds without async.
Testability Wire rules are tested through ScriptedSession + #[moq_clock::test], building a SubStream around a real stream even for pure arithmetic (owed/covered). 10 plain #[test]s, no clock, transport, or kio; two async scripted tests became plain ones. The serve loop's interleaving tests (update vs FIN, unanswered refusal, reorder grace, linger) still need the scripted transport and moq-clock, since that is where the waits are.
rust2-ts census (--census, rooted as named) lite::subscriber::TrackServeRun: 919 refusals / 484 fn instances (atomics 121, moq_clock::Instant 75, kio::Producer 63, kio::Mut 62, …) lite::upstream::Upstream: 4 refusals / 29 instances: 3 Vec<Range> clone/eq std gaps, plus moq_pattern::InvalidPattern via Error. The driver root still measures 919 / 484, identical to main.
FFI export Exports are model-level (track, group, frame handles over kio), with kio::wait per ffi-poll-migrate. No change. A foreign binding never drives one SUBSCRIBE's control stream; it reads the model the driver fills.
Compile (moq-net lib, -Ccodegen-units=1, CARGO_INCREMENTAL=0, user instructions, 3 runs) dev 75.63–75.67 G, release 218.76–218.80 G dev 75.62–75.70 G, release 218.81–218.91 G (≤ +0.05%, within noise)
Runtime (session bench: session_churn_*, session_join_relays, 2 interleaved runs per side, min of 2) baseline lite-06/07 rows: −16% to +4%, most within ±3%. The moq-transport-22 control rows, whose code is identical, moved −20% to +26%. No difference above this host's noise. The core adds one Vec<Range> clone per SUBSCRIBE and per update.

What this means

  • moq-net already has sans-IO's main property. I/O enters through the transport::poll traits, and every wait is a poll_* any executor (or a plain loop) can drive. moq-uring runs moq-net sessions on a sans-IO QUIC stack this way today. What a sans-IO protocol core adds on top is the absence of waiters. Zig's std.Io removes coloring by parking stacks. kio's &Waiter is the stackless half of that, and a sans-IO core removes even the waiter, at the cost of a driver that owns every wait.
  • The TypeScript win is real but does not reach a session. The core is almost translatable today; its 4 refusals are std gaps rust2's std-more work covers. A browser session still runs the driver, the model, kio, and moq-clock, which is rust2's model epic whatever the protocol style. Converting every protocol path would leave that 919 untouched.
  • Testability improves only where the logic was pure already. Some recent fixes on this path were pure rules that the core now tests directly, such as a lite-05/06 DROP reading as Old (moq-dev/moq#6820). More were interleavings, and those stay in driver tests in both styles: an unanswered SUBSCRIBE whose instance was replaced (moq-dev/moq#6816), a FIN racing the peer's close (moq-dev/moq#6741), an update racing a finished track (moq-dev/moq#5833), and demand held until groups drain (moq-dev/moq#4225).
  • The cost scales with model coupling. This path touches the model at about 10 sites. The lite and IETF publisher/subscriber (roughly 50k lines) touch it far more, and each touch becomes an effect variant plus a driver arm, with state split across two structs.
  • The waits quest's middle point (a core that holds kio waiter lists but does no I/O) is what moq-net's model already is. This spike gives no reason to push moq-net past it.

Recommendation

Don't move moq-net's protocol core toward sans-IO. Keep kio poll_* as the rule. Pull pure wire rules (version predicates, group arithmetic) into plain functions or structs where that turns async scripted tests into plain ones, as ordinary refactors, without an effect/transmit architecture. The TypeScript route is gated on translating the model, not on the protocol style.

Measurement notes

  • The host was saturated by parallel agents (load 28–44 on 24 cores). Compile cost was therefore measured in user instructions, which needed two fixes. perf stat --no-scale: on a hybrid CPU each core type's counter only runs while the process sits on that type, and perf's default scaling inflated the counts ±20%. The real cargo binary: the mbx cargo shim serves cached rlibs. With both fixes, runs agreed within 0.1%.
  • rmeta bytes change on any body edit in either style (they carry the crate's SVH), and cargo has no early cutoff, so every edit rebuilds dependents in both. The style only matters to an early-cutoff hash such as rust2's semantic API hash. Neither style here compiles an async fn.
  • The census used rust2-ts from github:kixelated/rust2#ts at today's main, with -- --package moq-wasm as rust2's own just ts census does.

Choices made without asking

  • Converted the subscriber side (relay's upstream SUBSCRIBE), not the publisher side: it has the richer response handling (START/END/DROP, bounded FINs, owed groups), so it is the harder test for sans-IO.
  • Message-level rather than byte-level seam (see above).
  • Prototype on a spike/ branch, results here, following moq-dev/moq#5851.
  • Recorded the findings and the decision as a Plan bullet in quest/m1/poll-first/README.md.

Decision

Decided by the maintainer (2026-10-28): no. moq-net's protocol core does not move toward sans-IO. kio poll_* stays the rule, and pure wire rules move into plain functions where that simplifies tests, as ordinary refactors. The lite::upstream extraction does not land. spike/sansio-upstream gets deleted once this PR merges. The README bullet now records the decision and no longer links the branch.

Suggested follow-ups

  • A compile-cost measurement recipe (only if a later quest needs one; the sibling poll-macro spike and async-feature both measure compile time). A just recipe or script that rebuilds one crate with the real cargo, CARGO_INCREMENTAL=0, -Ccodegen-units=1, and perf stat --no-scale -e instructions:u, summing both hybrid core types and reporting base vs head. Done when two runs on one tree agree within 0.1% on a loaded host.

Public API: none (the prototype is unmerged). Wire: none.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits October 28, 2026 00:02
The lite subscriber's upstream SUBSCRIBE was converted to a sans-IO core on
spike/sansio-upstream and compared with kio poll. Delete the quest and
record what it found in the poll-first epic's Plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Sans-IO compared quest(m1): sans-IO compared on the lite upstream SUBSCRIBE Oct 28, 2026
@kixelated
kixelated marked this pull request as ready for review October 28, 2026 15:26
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e8026df5-43f3-4831-9f9c-ce02c07e28b9
Commits

Reviewing files that changed from the base of the PR and between 84d9b01 and 695641a.

Files selected for processing: 3
  • quest/m1/poll-first/README.md
  • quest/m1/poll-first/sansio-compare.md
  • quest/m1/quic-api/waits.md
Files with no reviewable changes: 2
  • quest/m1/quic-api/waits.md
  • quest/m1/poll-first/sansio-compare.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Walkthrough

The documentation records comparison results and the decision to keep moq-net’s protocol core on kio poll. It also removes the Sans-IO comparison document and references to that document.

Priority: Low

Merge Risk: Minimal · up to 69564

The decision is recorded consistently and obsolete comparison references are removed; the PR is ready to merge.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title names the main change: a sans-IO comparison on the lite upstream SUBSCRIBE spike, and the PR's outcome is a recorded decision against moving moq-net's protocol core toward sans-IO. The title…
Description check Passed The description covers the changeset in detail. It explains the deletion of sansio-compare.md, the prototype on a spike branch, the measured results, and the maintainer decision recorded in the poll-f…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: a sans-IO comparison on the lite upstream SUBSCRIBE spike, and the PR's outcome is a recorded decision against moving moq-net's protocol core toward sans-IO. The title is specific and matches the changeset, which deletes the comparison quest, records the decision in the poll-first Plan, and removes a link in waits.md. It does not say the outcome was a rejection, but it is clearly related to the main change.

Full details: Description check

Explanation

The description covers the changeset in detail. It explains the deletion of sansio-compare.md, the prototype on a spike branch, the measured results, and the maintainer decision recorded in the poll-first README. It is related to the changeset, so the lenient check passes.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 0 files. (1 skipped: 1 unsupported.)

Finishing Touches
✨ Simplify code
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T18:27:30.913157Z 695641a New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed commit: 37040a8

No actionable bugs found in this documentation-only change. Retaining kio poll drivers while extracting pure wire rules is a reasonable simpler alternative to introducing an effect/transmit layer throughout the protocol; the report appropriately separates pure-rule tests from interleaving tests and acknowledges the remaining model translation work. Removing the completed comparison quest and its related link is consistent with recording the decision in the epic.

Verification limits: static review of the changed documents and surrounding plans. I did not execute the unmerged prototype, tests, rust2 census, or compile/runtime measurements; the reported results are author evidence, not independently verified performance or test results.

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 37040a80ea

ℹ️ 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".

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary: this PR deletes quest/m1/poll-first/sansio-compare.md, records the maintainer's decision (moq-net's protocol core doesn't move toward sans-IO; kio poll_* stays the rule) in the poll-first epic's Plan, and drops the related link from quest/m1/quic-api/waits.md. It changes docs only. CI is green (12/12), Codex reviewed head 37040a80ea and found no issues, quest check --base origin/main passes, and the head merges cleanly with current main. The prototype branch spike/sansio-upstream gets deleted after this merges, as the maintainer approved.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 28, 2026
@kixelated
kixelated added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit ca90117 Oct 28, 2026
11 checks passed
@kixelated
kixelated deleted the quest/m1/poll-first/sansio-compare branch October 28, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant