Repository navigation
Conversation
Quest-Claim: 5a55be3f472c7d70
…pe/session-closed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 4 minutes. View limit details
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. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head: 3be989f
No actionable findings.
Direction: the binding boundary is the right place to normalize clean session closes and reasonless SESSION_CLOSED resets to Closed. Direct session failures retain their structured reason. Removing the now-unreachable SessionClosed kind is consistent with the documented breaking API change. The regression tests cover both sides of direct lite/IETF sessions and the relayed lite reset contract.
Verification limits: GitHub-only static review of the conversion, wire mappings, affected tests, and binding impact. I did not run builds, binding generation, tests, or interop. The documented relayed moq-transport limitation remains outside this change.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- A session end reads as
Closedfrom either side:From<moq_net::Error>foldsSession(SessionError::Cancel)and everyStream(StreamError::Session(_))intoMoqError::Closed, so a clean close and a receivedSESSION_CLOSEDreset read the same, while a session that failed keeps its scope, code, and kind. MoqProtocolKind::SessionClosedis removed: the variant and itsstream_kindarm go, the GoProtocolKindSessionClosedalias goes with them, and the checked-in Dart bindings are regenerated against the shifted ordinals (the lift'sindex + 1cases still line up).- Tests: the unit test pins both sides of the mapping and that a failed session keeps
Unauthorized;a_closed_session_ends_its_tracks_as_closedcovers publisher- and subscriber-side closes onmoq-lite-07-wipandmoq-transport-18; the moq-net socket test now asserts theStream(Session(_))variant the mapping relies on instead ofis_err(). - Quest:
quest/m2/api-shape/session-closed.mdand its milestone bullet are deleted, and nothing else in the tree still links to the slug.
I checked the removed reason-carrying case: StreamError::Session only decodes from stream code 0x3, and moq-net's local construction sites pass Session(Cancel)/Session(Internal), so folding it into Closed cannot hide a reason a reader could otherwise act on; real failures still arrive as Error::Session(<code>).
ℹ️ The hand-written moq-c does not carry the Closed reading into the C line
c/moq and cpp/moq are generated from moq-ffi, so they adopt MOQ_ERROR_CLOSED for the session-end rows and drop MOQ_PROTOCOL_KIND_SESSION_CLOSED without a Rust change. rs/moq-c, the hand-written line that replaces them after this cut, keeps the kind and still reports a clean session close as MOQ_ERROR_MOQ with {SESSION, 0, CANCEL}, so a C caller moving between the two surfaces gets the old reading back. Nothing here is blocking — the track path already reads the same on both sides in rs/moq-c, which is what the quest asked for — but the generated-to-hand-written transition is where that difference will surface.
Technical details
# C surfaces disagree on how a session end reads
## Affected sites
- `rs/moq-c/src/error.rs:426` — `protocol_of_net` returns `Some` for `Error::Session(Cancel)` and `Error::Stream(StreamError::Session(_))`, both reaching callbacks as `MOQ_ERROR_MOQ`.
- `rs/moq-c/src/error.rs:129` — `MOQ_PROTOCOL_KIND_SESSION_CLOSED = 12` now exists only here; the generated `c/moq` header loses it with this PR.
- `c/moq/README.md` — generated from `rs/moq-ffi`, so `c/moq` and `cpp/moq` pick up `MoqError::Closed` automatically.
- `doc/lib/c/index.md:112` — documents `MOQ_ERROR_*` and `moq_error_protocol()` without describing a session end on either surface.
## Required outcome
- A caller migrating from the generated C package to `rs/moq-c` is not surprised by `MOQ_ERROR_MOQ` plus a protocol record where it used to get a closed code.
## Open questions for the human
- Is the verbatim `{scope, code, kind}` report deliberate for the low-level C API, or should `rs/moq-c` gain a `Closed`-shaped status at the cut? Either answer is fine here; a sentence in the C docs at the migration point would pin it.deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Finishes quest
quest/m2/api-shape/session-closed.md(deleted here).Problem
A binding saw
MoqError::Closedfor a session that closed only when two endpoints shared one in-process origin (moq-dev/moq#8373). Across a real session the same event surfaced as a protocol error, sois_shutdown/IsShutdowntreated an expected teardown as a failure:Stream(Session(Cancel))ClosedClosedStream(Session(Internal)), a received SESSION_CLOSED resetProtocol { Stream, SessionClosed, 0x3 }ClosedSession(Cancel)Protocol { Session, Cancel, 0x0 }ClosedUnauthorized)Session(<code>)Protocol { Session, <kind> }I measured each row with temporary probes over the mock transport before changing anything: lite-05, lite-07, moq-transport-18 and -22, with the publisher aborting, dropping, or closing, and the subscriber closing.
Approach
rs/moq-ffi/src/error.rs:From<moq_net::Error>mapsSession(SessionError::Cancel)and everyStream(StreamError::Session(_))toClosed. A SESSION_CLOSED reset carries no reason, so a local cause and a received one now read the same. A session that failed keeps its scope, code, and kind.MoqProtocolKind::SessionClosedis removed, because no conversion can produce it any more. The GoProtocolKindSessionClosedalias goes with it, and the checked-in Dart bindings are regenerated.Tests:
a_closed_session_is_closed_on_either_side(unit) replaceslocal_session_close_is_closed, which pinned a received SESSION_CLOSED toProtocol.a_closed_session_ends_its_tracks_as_closed(moq-ffi, in-memory sessions on lite-07 and moq-transport-18): a reader whose session is closed by the publisher or by itself getsClosed. Before this change it gotProtocol { Session, Cancel }.closing_an_endpoint_ends_its_sessions(moq-net, through a relay) now asserts that the peer readsStream(Session(_)), which is the moq-net contract the mapping relies on. Before, it asserted onlyis_err().js/net and moq-c
Closederror. A socket endpoint close and a received reset are bothStream(SessionClosed), and a clean session close isSession(Cancel)on either side, so local and peer closes already read the same.MOQ_ERROR_MOQwith{STREAM, 0x3, MOQ_PROTOCOL_KIND_SESSION_CLOSED}, and a clean close as{SESSION, 0, CANCEL}. Its callbacks already end with0for a clean end. moq-c has no local/peer split to fix, and a new status would break every C caller that matchesMOQ_ERROR_MOQ.Impact
Closedinstead ofProtocol.MoqProtocolKind::SessionClosedis removed from Rust, Python, Kotlin, Swift, and Dart, andProtocolKindSessionClosedfrom Go. All laterMoqProtocolKindordinals shift by one, so regenerate the bindings together with the native library.Protocolwith kindSessionClosed, or with session-scope kindCancel, should matchClosed.is_shutdown/isShutdown/IsShutdownnow cover these cases with no change.Alternatives
SessionClosedas a kind that is never reported. Rejected: a kind no conversion produces is misleading API. Removing it turns stale matches into compile errors in the typed bindings instead of branches that silently never run.Session(_)close, failures included, toClosed. Rejected: that would hide the reason (Unauthorized,Expired,ProtocolViolation) a caller can act on.Choices made without asking
MoqProtocolKind::SessionClosed, rather than keeping a variant nothing reports.Session(Cancel)) map toClosed. Failed sessions keep theirProtocoldetails.Closedkind (reasons above).moq-transport limit (not fixed)
When the peer whose session closed sits behind a relay, the relay ends a moq-transport subscription with PUBLISH_DONE
INTERNAL_ERROR, because that registry has no session-closed status. The reader then seesRemote(0), which is an internal error in the bindings, notClosed. Direct moq-transport sessions are covered, since a clean close decodes toSession(Cancel). Mixed lite/moq-transport chains lose the cause at the moq-transport hop the same way. Per AGENTS.md, moq-transport is a compatibility layer, so this PR leaves it alone.Follow-ups
Pre-existing failures that block a clean local
just checkon macOS. No open PR fixes them:moq-carla-bridge's module docs link items that are compiled only on Linux, socargo doc -D warningsfails on macOS.py/moq-rsdocs build fails on the missingmoq.media.catalogautosummary stub.sh/version.test.py: two release-cut tests fail on main.check cppfails on macOS hosts on a-Wl,-lintlflag.Checks
Run on macOS:
cargo fmt --all --check;cargo clippy -p moq-ffi -p moq-net --all-targets -D warnings;cargo doc -D warnings -p moq-ffi -p moq-net;cargo nextest run -p moq-ffi(323 passed); moq-netsocket::integration tests (28 passed);just py test(127 passed).just checkmodules that passed: go, kt, swift, dart (Dart bindings regenerated withjust dart generate), c, obs_compile, biome, markdown, gh, quest, shell, toml, nix, justfile, drill, large_files, text.just test interop --all: 182 cases passed. The only failures are thefinish cpp -> *cases, run underenv -u MOQ_QUESTbecause the session leaks that variable and the binaries reject it.origin/mainat d9800c1:check rslint:cargo doc -D warnings -p moq-carla-bridgefails with three unresolved links. The crate is#![cfg(target_os = "linux")], so on macOS its module docs link items that are not compiled. Linux CI is unaffected.check pydocs: autosummary reportsstub file not found 'moq.media.catalog'(py/moq-rs/docs/index.md:147).check cpp:clang++: error: -Wl,-lintl: 'linker' input unusedfrom the host toolchain.test gh: two cases insh/version.test.py(test_cut_promotes_only_published_library_dependencies_and_preserves_exemptionsandtest_release_plz_keeps_manual_cut_despite_breaking_commits).finish cpp -> *: all 9 fail on base too. moq-dev/moq#8407 quarantines them.Review
quest review)01a1c371-c2fa-7a12-959c-80ce712f71bd, round 1🤖 Generated with Claude Code
(Written by Claude Opus 5.5)