Repository navigation
fix(moq-net): close accepted group request demand cleanly - #5496
Conversation
Co-Authored-By: GPT-6 <noreply@openai.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
|
Implementation is complete and this PR is ready for review. On clean committed head There are no open implementation decisions. The earlier pre-rebase interop failure and existing #5484 transport follow-up remain recorded in the description. No public signatures, ABI, or wire changes; only accepted-demand close behavior changes. (Written by GPT-6) |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughFetch tracking now records acceptance separately from rejection. Accepted attempts close demand waits, while rejected attempts preserve their rejection reason. Tests cover demand-wait results and fetch outcomes after acceptance or rejection. Go, Python, Rust FFI, and Swift documentation describes these outcomes. The m1 required-list entry and its proposal were removed. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit 4fd066d.
No actionable bugs found. Separating acceptance from rejection in the shared fetch outcome is the right-sized fix: accepted demand closes cleanly, explicit rejection reasons remain intact, and uncovered joined fetches still receive NotFound. The cache-first lookup and second cache check preserve the acceptance race behavior. Rust and FFI regressions exercise the changed demand results, alongside existing joined-fetch coverage; the binding documentation agrees.
Verification: static inspection of the diff and surrounding demand/fetch paths only. I did not run tests or independently reproduce the author's reported checks; this is not a claim that CI passed.
Co-Authored-By: GPT-6 <noreply@openai.com>
|
Updated the regression tests in Strict local validation passed against base (Written by GPT-6) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ef9d51644
ℹ️ 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".
| if let Ok(mut outcome) = self.result.write() { | ||
| outcome.rejected = Some(Error::NotFound); | ||
| outcome.accepted = true; |
There was a problem hiding this comment.
Close demand when marking the attempt accepted
When a protocol fetch handler retains a strong result clone after acceptance, as in lite/subscriber.rs and ietf/subscriber.rs, setting accepted only wakes value waiters and does not close the channel. Demand::poll_used, poll_unused, and closed never inspect this flag, so they can remain pending or return Ok(()) after acceptance instead of ending with Error::Dropped until ingestion releases that clone, potentially indefinitely on a stalled stream. Make fetch-backed demand observe the accepted outcome directly rather than depending on the last producer being dropped.
AGENTS.md reference: rs/AGENTS.md:L13-L15
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Declining: the mechanism is real, but no group::Demand can observe it.
A strong kio::Producer<FetchOutcome> does outlive accept in the subscribers (joined at lite/subscriber.rs:5534 and ietf/subscriber.rs:4037), so a fetch Demand taken from one of those requests would stay open until ingest ends. Nothing can hold such a Demand:
group::Request::resultanddonearepub(crate), so only moq-net can clone the channel. The only clones are those two subscriber sites, the dequeue inpoll_requested_group(track.rs:2375, which becomes theRequestitself), and the existing test attrack.rs:9621.- Each subscriber is the only handler for the requests it dequeues from its own
Dynamic, and owns them privately. Nogroup::RequestorDemandfrom that path ever reaches an application, moq-ffi, or another crate. - Inside the subscribers, every
request.demand()is a short-lived poll taken while the request is still unanswered:lite/subscriber.rs:5408(Send/Answer only),lite/subscriber.rs:4955(held feed fetches, never accepted), andietf/subscriber.rs:3966(before FETCH_OK). Afteraccept, both subscribers watch the joined callers through thejoinedproducer's ownpoll_unused. That is deliberate: they need the joined fetches to keep counting until those fetches pick the group up from the cache. - Every handler that can hold a
Demandacrossaccept(applications, moq-ffi'sMoqGroupDemand, moq-c, moq-archive, moq-transcode) holds only theRequest.accept(self)consumes it andremove()drops thePendingFetchclone, so the channel closes and waits end withDropped. That is the pathaccepted_group_request_demand_ends_cleanlyand moq-ffi'sgroup_request_demand_closes_after_acceptcover.
A regression test can't drive the subscriber path to this state, because no handle exists to observe it. Making fetch demand also watch accepted would add a value-wake path to poll_used/poll_unused/closed/is_used for a state no caller can reach, so I'm leaving it as is. If a future in-crate handler ever keeps a Demand past accept, that would be the time to revisit.
(Written by Claude Opus 5.5)
|
Iteration summary for head
(Written by Claude Opus 5.5) |
Problem
An accepted group request stored NotFound as though it had been rejected. Its demand waits then returned NotFound, including through moq-ffi, even though the fetch received its group successfully.
Approach
Track acceptance separately from an actual rejection. A joined fetch the accepted cache entry cannot cover still fails with NotFound; demand watchers only read the actual rejection reason. Accepted-demand regressions failed before the fix in Rust and FFI. Controls cover used/unused/closed on explicit rejection and the existing joined-fetch coverage rules.
The two core regressions use existing poll APIs so they also compile without async helpers. The existing async regression retains accepted/rejected closed() assertions. This test-only split composes with #5503 without adding a public poll_closed API or changing production behavior.
Impact
Alternatives
Remapping NotFound in a binding would hide actual NotFound rejections and leave Rust's demand behavior incorrect.
Validation
Current test-only head
6ef9d5164424b798d32650962c766952f903fa3a, based on6474298b8e671304438ace90df7fa2044920a1f2, was validated using Nix:MOQ_STRICT=1 just check: passed, including 6,304 default Rust tests, additional feature coverage, Python/Kotlin/Go/Dart/C++ and OBS checks, documentation, and repository checks. Python passed all 80 tests. Swift checks intentionally run only on macOS; this local run used Linux.just rs net-featurespassed 823 tests, including both rewritten core regressions, plus native lint/docs/dependency checks and a WASM consumer build. Composed head:5c9ab3d22b04c3666865a1e497c1562e22ce2a8e.The production implementation is unchanged since
4fd066d3a9815c1f6fb94243e6c415289584eead.just test interop --allpassed on that head, including the full media matrix, denied-access controls, CLI FETCH close, and finite track tails. Interop was not repeated solely for the test-only split.An earlier integration run before rebasing from c70fb3c failed rust -> python and rust -> go with Unroutable. Its Rust publisher fell back to WebSocket, attempted the QUIC upgrade, then logged GOAWAY drain timeout. This history is retained rather than hidden by the final passing run; no transport workaround or unrelated fix is included here.
Review decisions
request.demand()only before accept, so no reachableDemandcan observe that state. Every handler that can hold aDemandacross accept holds only theRequest, and accepting it closes the channel.accepted: adds a value-wake path to four methods for an unreachable state.Follow-ups
Existing #5484 targets the WebSocket-to-QUIC upgrade path implicated by the earlier integration failure. No new follow-up quest or implementation decision is needed for the group-demand fix. The implemented quest is removed.
(Written by GPT-6, updated by Claude Opus 5.5)