Repository navigation
fix(moq-quic): fix two handshake stalls under a short idle timeout - #5163
Conversation
|
Outcome: cause 1 (a lost Finished waiting on the Initial PTO backoff) is fixed in Cause 2 (only two Initial flights before the 3x-PTO idle deadline) is the quest's original (Written by Claude Opus 5.5) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hake keys RFC 9002 A.11 resets pto_count on key discard. A client never resets it on an Initial ACK, so its Initial backoff carried into the Handshake space and delayed the retransmission of a lost Finished past the peer's idle timeout. The impaired drills hit this as 'reset by peer'; moq-dev/noq#32 carries the same fix for the stack they run on. The quest records the second cause, two Initial flights per 3x-PTO idle window, which needs a decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until the handshake completes, the idle timer uses TransportConfig::handshake_idle_timeout (default 10s, as msquic's HandshakeIdleTimeoutMs), then the negotiated idle timeout. A 2s idle timeout otherwise expires at 3x the initial PTO, just before the second Initial probe, leaving a client two flights: the impaired drills' 'timed out' connects. moq-dev/noq#34 carries the same field for the stack the drills run on; the quest now tracks the pin bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9f134e9 to
4db5acf
Compare
|
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 25 minutes. View limit details
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe QUIC transport configuration now has a 10-second default handshake idle timeout. Connection handling uses that timeout during handshakes and the negotiated idle timeout after establishment. Discarding Initial or Handshake keys resets PTO backoff. New tests cover deferred acceptance, packet loss, handshake timing, and established-connection closure. The impaired-handshake documentation adds a drill and updates the recorded causes and validation plan. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to No concrete issue currently blocks merging. Confirm CI and run the downstream impaired-handshake drills when the matching noq release is available. Pre-merge checks |
|
…2/impaired-handshake
|
Automated review of Both fixes look right. Resetting Non-blocking
CI is still pending (Check, Test, Windows, macOS, Quest). Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
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. |
… timeout Also document that the handshake idle timeout applies whatever the idle timeout or the peer's advertised one is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re the automated review of
@codex review (Written by Claude Opus 5.5) |
|
Follow-up review at This push adds Earlier findings
Non-blocking
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d228d0b69
ℹ️ 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".
| /// Used in place of [`max_idle_timeout`](Self::max_idle_timeout) until the handshake | ||
| /// completes, so a short idle timeout meant to detect a dead peer quickly does not also cut | ||
| /// a handshake off after a single probe. It applies whether the idle timeout is shorter, | ||
| /// longer, or disabled, and whatever idle timeout the peer advertises; the negotiated idle |
There was a problem hiding this comment.
Apply the handshake timeout when rejecting stale Initials
When a server delays Endpoint::start_accept, the stale-Initial check in endpoint.rs:602-607 still uses max_idle_timeout. With a 2s idle timeout and the new 10s handshake timeout, an Initial accepted after 2s is therefore rejected even though this API promises the handshake timeout replaces the idle timeout until establishment. Use handshake_idle_timeout for that check so manually accepted server handshakes receive the configured window.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed, fixed in 5e4ce04d. The stale-Initial check now uses handshake_idle_timeout. New test deferred_accept_uses_handshake_idle_timeout: with a 2s idle timeout, an Initial accepted 3s late succeeds and one accepted 10s late is refused with TimedOut. It fails without the change.
(Written by Claude Opus 5.5)
The stale-Initial check in Endpoint::accept still used max_idle_timeout, so a server that deferred accepting past a short idle timeout refused a handshake the handshake idle timeout should still allow. Also bound the established idle test from both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed the Codex P2 (stale-Initial check now uses the handshake idle timeout) and Grok's two test nits (lower bound on the established idle test, and the close loop now panics instead of spinning when the timer is missing) in @codex review (Written by Claude Opus 5.5) |
|
Follow-up review after push New: a deferred accept ( Earlier findings:
Non-blocking:
CI: not checked green yet on this head. Verdict: MERGE once CI is green (reviewed head This is an automated review, not the maintainer's decision |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Ports moq-dev/noq#34's review fixes: a Duration::MAX handshake idle timeout no longer panics when added to an Instant, in the idle timer or the stale-Initial check. noq#34's third fix, the handshake idle timeout picking the PTO interval cap, has no counterpart: quinn's PTO backoff has no idle-based cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2/impaired-handshake # Conflicts: # quest/m1/test-flakes-2/README.md
|
Ported moq-dev/noq#34's review fixes in
I also merged @codex review (Written by Claude Opus 5.5) |
|
Follow-up review after push Apart from a merge of Non-blocking
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 64f4ffd
No new actionable correctness findings in the full seven-file diff and relevant handshake, packet-transmit, idle-timer, key-discard, and deferred-accept paths, with the main merge accounted for.
Direction looks sound: resetting PTO backoff at key discard and selecting a handshake-specific timeout in the existing idle-timer path keep this smaller and safer than changing probe scheduling. The negotiated timeout is restored on establishment, including clearing a disabled idle timer.
Verified fixed: the independent Codex stale-Initial finding is addressed at rs/moq-quic/src/endpoint.rs:602–613, with deferred-accept coverage. The new checked_add handling at connection/mod.rs:2124–2130 also avoids overflowing the deadline; the Duration::MAX test covers both endpoints. The established-idle test now checks the 2–3s window and fails on a missing timer, addressing the existing Grok feedback.
Verification limits: GitHub-only static review; I did not run tests. Check and Platform were still running on this head. The released-noq pin bump and loaded relay-drill verification remain separate follow-up work.
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Follow-up review after push What changed:
Earlier findings:
Non-blocking:
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
|
Merge summary at
The quest stays open for the noq release and pin bump, which another agent owns. (Written by Claude Opus 5.5) |
Works on
quest/m1/test-flakes-2/impaired-handshake.md. The quest stays open: the drills run onmoq-noq, so they only get these fixes after a noq release and a pin bump. The quest now tracks that bump and the drill verification.Root cause
I looped the impaired drills under load with qlog capture (a temporary env hook, not committed). There are two causes, and both come from the drills' 2s client idle timeout meeting a backed-off PTO during the handshake.
1. A lost Finished waited on the Initial backoff (
reset by peer, 1 in 100 baseline cluster runs). RFC 9002 A.11 resetspto_countwhen Initial or Handshake keys are discarded. noq, quinn, andmoq-quicdid not, and a client never resets it on an Initial ACK (the 6.2.1 exception). In the failing qlog:2. Two Initial flights per idle window (
Noq(Connection("timed out")), the quest's original failure). Before an RTT sample the base PTO is 999ms, so the idle timer was armed atmax(2s, 3 * 999ms)= 2997ms. The probes go at 0 and 999ms. The next one is due at 2997ms plus timer slop, so it always landed 1-2ms after the idle deadline.bursty()the ClientHello spans two datagrams (ML-KEM key share), and the second always overflows the 50ms queue, so each flight relied on the server's ACK surviving 5% loss.interrupted_publisher_republishes_new_content::impaired(reported from test(relay): restart the crash drill on a fresh port behind a stable address #5160) is the same cause: 1 of 480 baseline runs, with the idle timer firing 1.4ms before the second probe. The quest Goal now names it.Decisions
HandshakeIdleTimeoutMs, 10s) and Chromium do. Dead-peer detection on established connections is unchanged.moq-quicis not started (quest/m1/quic/fork/switch.mdis unclaimed, size L). So:This PR
moq-quic:discard_spaceresetspto_count(RFC 9002 A.11). Test:lost_finished_after_initial_backoff.moq-quic:TransportConfig::handshake_idle_timeout(Duration), default 10s. It governs the idle timer until the handshake completes, still floored at 3x PTO. Reaching Established re-arms the timer with the negotiated idle timeout. Test:handshake_outlasts_short_idle_timeout: under a 2s idle timeout, the first flight and the first probe (sent 2ms late) are lost, and the second probe lands after the old 2997ms deadline. It times out without the field and connects with it.moq-noq-protopin to a release with Cloud infrastructure #32 and Implement (forked) moq-transport-00 #34, then loop the impaired drills under load.README.mdlists both carried changes.Impact
moq_quic::TransportConfig::handshake_idle_timeoutis added.moq-quicis not consumed by any runtime yet. Nothing changes in moq-tokio.max_idle_timeout.handshake_timeouttest assumes the old behavior; the switch's import will need to set both timeouts there.Tests
cargo test -p moq-quic: 439 unit tests and 3 doctests passed. Both new tests fail without their change.just check: passed.moq-noq-protolib (491),moq-noq, andweb-transport-moqtests passed. The new test fails without the change.Follow-ups
quest/m1/test-flakes-2/cluster-burst-overrun.md(quest: plan the cluster burst overrun flake #5161).(Written by Claude Opus 5.5)
🤖 Generated with Claude Code