Skip to content

Don't rank a quiche send half the peer already stopped - #391

Merged
kixelated merged 1 commit into
mainfrom
claude/quiche-priority-drain-lifecycle
Sep 4, 2026
Merged

Don't rank a quiche send half the peer already stopped#391
kixelated merged 1 commit into
mainfrom
claude/quiche-priority-drain-lifecycle

Conversation

@kixelated

@kixelated kixelated commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #390, narrowed. That PR's review turned up three lifecycle defects; this lands the one whose fix is unambiguous, plus test hardening. The ranking math in ez::Priorities is unchanged — it was correct.

The fix

accept_bi registered the accepted send half unconditionally. If the peer sends STOP_SENDING before we accept, the first SendState::flush already comes back closed — and nothing will ever flush it again, since notify skips a closed state and quiche won't report a collected stream writable.

The stale entry in the driver's send map predates the ranking. What #390 added is that the stream also holds an urgency band for the life of the connection, so a peer repeating this can push live streams toward the overflow band and flatten their relative order.

Only live send halves are tracked now. The application still receives the handle and sees Stop(code) on first use (or Closed if quiche already discarded the code). priority.insert moves out of push_accept_bi into the driver, which is the half that knows whether the flush survived.

Test hardening

  • tests/priority.rs had no bounded await anywhere, so a stalled stream parked finished.recv() or a writer join until CI's job timeout instead of failing. Now bounded at 30s.
  • rebalance_matches_a_full_ranking — 10k pseudorandom ranking operations checked against a from-scratch ranking after every step. The early exit in rebalance is the one part of the mapping that isn't locally obvious, and it only executes once the overflow band is occupied, so the run is weighted to climb past BANDS levels and saw_overflow asserts it actually got there.

Deliberately not included

Two findings from the #390 review are left open, because the obvious fix for them turned out to be wrong:

  1. A stream loses its rank while quiche is still sending it. SendState reports closed the moment its FIN reaches quiche, so the band is freed while quiche still has buffered bytes; the promotion that follows can lift a lower-priority stream level with data that outranks it.
  2. A set_priority immediately before finish is dropped, since Priorities::remove clears the pending update.

The natural fix — hold the rank until qconn.stream_capacity(id) errors — is sound but not complete: quiche only drops a stream once is_complete(), which for a bidirectional stream requires both directions. A bidi stream whose send half is finished and acked keeps returning Ok while the peer's half stays open, so the rank would never be reaped. That is worse than the bug it fixes, and "finish your send, keep reading the response" is the normal bidi pattern. quiche 0.29 exposes no send-side drain predicate (stream_closed reports FIN written, is_flushable isn't on Connection), so this needs a different mechanism and belongs in its own change.

cargo test -p web-transport-quiche green; cargo clippy --all-targets -D warnings and cargo fmt --check clean.

🤖 Generated with Claude Code

(written by claude-opus-5)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 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-09-02T23:10:44.305709Z 600d80d 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.

@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: 02a3cc43a1

ℹ️ 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 on lines +894 to +899
let drained: Vec<StreamId> = self
.draining
.iter()
.copied()
.filter(|id| qconn.stream_capacity((*id).into()).is_err())
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Release ranks once buffered bytes leave the scheduler

stream_capacity only errors when quiche collects the completed stream, which for a normally finished send is after its data is acknowledged, rather than when its buffered bytes have left the scheduler. If 255 distinct finished-but-uncollected priority levels rank ahead of new active streams—for example with a configured stream limit above 255—the active levels all map to the overflow urgency and round-robin despite having different send orders, even though the retained streams have no bytes left to schedule. Track scheduler drain separately or otherwise exclude ranks that are only awaiting collection so the documented top-256 ordering remains effective.

AGENTS.md reference: AGENTS.md:L48-L49

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: d4235d50-ce85-4be1-8f58-fc72879c5057

📥 Commits

Reviewing files that changed from the base of the PR and between 02a3cc4 and 600d80d.

📒 Files selected for processing (2)
  • rs/web-transport-quiche/src/ez/driver.rs
  • rs/web-transport-quiche/src/ez/priority.rs

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.


Walkthrough

The driver now registers an accepted stream in self.send and the priority ranking only when its send half remains live after the first flush. push_accept_bi continues to deliver the stream handle without registering priority. Priority tests now validate incremental rebalance against a full ranking across 10,000 fixed-seed operations. The integration test also applies a 30-second timeout to the priority exchange.

Merge Risk: ⚪ Minimal · up to 600d8

The change prevents already-closed send halves from occupying scheduling state while preserving the existing application-facing stream behavior; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly describes the main change: avoiding priority ranking for a quiche send half that the peer already stopped.
Description check ✅ Passed The description directly explains the send-half lifecycle fix, priority behavior, regression tests, timeout hardening, and intentionally deferred findings.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/quiche-priority-drain-lifecycle

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.

…opped

`accept_bi` registered the accepted send half unconditionally. If the peer
sends STOP_SENDING before we accept, that first `SendState::flush` already
came back closed, and nothing will ever flush it again: `notify` skips a
closed state and quiche won't report a collected stream writable.

The stale entry in the driver's send map predates the priority ranking.
What the ranking added is that the stream also holds an urgency band for
the life of the connection, so a peer repeating this can push live streams
toward the overflow band and flatten their relative order.

Track only live send halves. The application still receives the handle and
sees `Stop(code)` on first use, or `Closed` if quiche had already discarded
the code. `priority.insert` moves out of `push_accept_bi` into the driver,
which is the half that knows whether the flush survived.

Also bound `tests/priority.rs`, whose every await was unbounded — a stalled
stream parked `finished.recv()` or a writer join until CI's job timeout
rather than failing the test.

Tests: `rebalance_matches_a_full_ranking` drives 10k pseudorandom ranking
operations against a from-scratch ranking. The early exit in `rebalance` is
the one part of the mapping that isn't locally obvious, and it only runs
once the overflow band is occupied, so the run is weighted to climb past
`BANDS` levels and `saw_overflow` asserts it got there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the claude/quiche-priority-drain-lifecycle branch from 02a3cc4 to 600d80d Compare September 2, 2026 23:07
@kixelated kixelated changed the title Hold a quiche stream's priority rank until quiche drains it Don't rank a quiche send half the peer already stopped Sep 2, 2026

@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: 600d80d289

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

// again would strand it here for the life of the connection: `notify` skips
// a closed state and quiche won't report a collected stream writable. The
// application still gets the handle, which reports the stop on first use.
let live = !state.is_closed();

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 Add regression coverage for stopped-before-accept streams

No test drives the fixed scenario: a peer-opened bidirectional stream whose send half receives STOP_SENDING before accept_bi initializes it. The new randomized test covers only Priorities bookkeeping, while the integration-test change only adds a timeout, so neither would fail if this lifecycle fix were removed or if SendState::flush stopped marking this state closed. Add a regression test that exercises this ordering and verifies the stopped send half is not retained or ranked.

AGENTS.md reference: AGENTS.md:L46-L47

Useful? React with 👍 / 👎.

@kixelated
kixelated merged commit d971416 into main Sep 4, 2026
1 check passed
@kixelated
kixelated deleted the claude/quiche-priority-drain-lifecycle branch September 4, 2026 18:18
@moq-bot moq-bot Bot mentioned this pull request Sep 4, 2026
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