Skip to content

fix(moq-quic): fix two handshake stalls under a short idle timeout - #5163

Merged
kixelated merged 9 commits into
mainfrom
quest/m1/test-flakes-2/impaired-handshake
Oct 10, 2026
Merged

kixelated merged 9 commits into
mainfrom
quest/m1/test-flakes-2/impaired-handshake

Conversation

@kixelated

@kixelated kixelated commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Works on quest/m1/test-flakes-2/impaired-handshake.md. The quest stays open: the drills run on moq-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 resets pto_count when Initial or Handshake keys are discarded. noq, quinn, and moq-quic did not, and a client never resets it on an Initial ACK (the 6.2.1 exception). In the failing qlog:

  • The client took 5 Initial PTOs, sent its Finished at 3194ms, and armed the Handshake PTO 2343ms out.
  • The Finished was lost. The server cannot read 1-RTT without it, so the client's keep-alives were dropped unread.
  • The server idled out at 5373ms. The client's probe was due at about 5537ms and drew a stateless reset.

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 at max(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.

  • On 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.
  • With fix 1 applied to noq, this was 3 of 300 cluster runs.
  • 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

  • Cause 2: which fix?
    • ✅ A handshake idle timeout separate from the idle timeout, as msquic (HandshakeIdleTimeoutMs, 10s) and Chromium do. Dead-peer detection on established connections is unchanged.
    • Cap the handshake probe interval, as picoquic does. A naive cap broke the PTO deadline's monotonic invariant and caused a PTO storm.
    • Floor the idle timer at 3x the backed-off PTO. This slows dead-peer detection whenever probes are outstanding.
  • The drills still run on noq, and the switch to moq-quic is not started (quest/m1/quic/fork/switch.md is unclaimed, size L). So:
    • ✅ Add the same field to moq-dev/noq as a separate PR stacked on Cloud infrastructure  #32, then release and bump the pin.
    • Wait for the switch.
  • ✅ Don't offer fix 1 upstream to quinn-rs/quinn or n0-computer/noq.
  • ✅ Keep the quest open in this PR, since the drills can't pick up the fix here.

This PR

  • moq-quic: discard_space resets pto_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.
  • The same fixes for the stack the drills run on: moq-dev/noq#32 (being merged and released separately) and moq-dev/noq#34 (draft, stacked on Cloud infrastructure  #32).
  • The quest is resized to [XS]: bump the moq-noq-proto pin to a release with Cloud infrastructure  #32 and Implement (forked) moq-transport-00 #34, then loop the impaired drills under load. README.md lists both carried changes.

Impact

  • Public API: moq_quic::TransportConfig::handshake_idle_timeout is added. moq-quic is not consumed by any runtime yet. Nothing changes in moq-tokio.
  • Behavior: a handshake to an unreachable peer now ends after the handshake idle timeout rather than max_idle_timeout.
    • With quinn's default 30s idle timeout, that is sooner.
    • With an idle timeout under 10s, it is later. The drills' clients will wait 10s instead of about 3s, which the quest checks against the crash drill's reconnect loop.
    • quinn's async handshake_timeout test assumes the old behavior; the switch's import will need to set both timeouts there.
  • Wire: none.

Tests

  • cargo test -p moq-quic: 439 unit tests and 3 doctests passed. Both new tests fail without their change.
  • just check: passed.
  • noq Implement (forked) moq-transport-00 #34: moq-noq-proto lib (491), moq-noq, and web-transport-moq tests passed. The new test fails without the change.
  • Drill loops (local builds) are described under Root cause. Verifying the drills on the released noq is the quest's remaining work.

Follow-ups

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: cause 1 (a lost Finished waiting on the Initial PTO backoff) is fixed in moq-quic here and in moq-dev/noq#32. The drills pick that fix up after a moq-noq-proto release and a pin bump.

Cause 2 (only two Initial flights before the 3x-PTO idle deadline) is the quest's original timed out failure and the republish drill's. It is still open and needs a maintainer decision. The recommendation is a separate handshake idle timeout in moq-quic. The PR stays a draft until that decision is made.

(Written by Claude Opus 5.5)

kixelated and others added 3 commits October 9, 2026 19:34
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>
@kixelated
kixelated force-pushed the quest/m1/test-flakes-2/impaired-handshake branch from 9f134e9 to 4db5acf Compare October 10, 2026 02:35
@kixelated kixelated changed the title fix(moq-quic): reset the PTO backoff when discarding handshake keys fix(moq-quic): fix two handshake stalls under a short idle timeout Oct 10, 2026
@kixelated
kixelated marked this pull request as ready for review October 10, 2026 03:10
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →Review in Change Stack →

Warning

Review limit reached

You'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.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 78ec28e3-beed-4a70-9913-f5a9b1324603

📥 Commits

Reviewing files that changed from the base of the PR and between 5e4ce04 and d515467.


📒 Files selected for processing (9)
  • quest/m1/js-restart-keeps-request.md
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/impaired-handshake.md
  • quest/m1/webgpu-safari.md
  • rs/moq-quic/README.md
  • rs/moq-quic/src/config/transport.rs
  • rs/moq-quic/src/connection/mod.rs
  • rs/moq-quic/src/endpoint.rs
  • rs/moq-quic/src/tests/mod.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 365390ed-4c1a-4e57-921a-eac333908f61



📥 Commits

Reviewing files that changed from the base of the PR and between 1eb5aeb and 5e4ce04.




📒 Files selected for processing (7)
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/impaired-handshake.md
  • rs/moq-quic/README.md
  • rs/moq-quic/src/config/transport.rs
  • rs/moq-quic/src/connection/mod.rs
  • rs/moq-quic/src/endpoint.rs
  • rs/moq-quic/src/tests/mod.rs



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 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 5e4ce

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 | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely identifies the two handshake stalls and their short idle-timeout context.
Description check Passed The description directly explains the handshake failures, implemented fixes, tests, behavior changes, and remaining downstream verification.
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: Docstring Coverage

Explanation

Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. (3 skipped: 3 unsupported.)






✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch quest/m1/test-flakes-2/impaired-handshake



✨ 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.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Automated review of db8d267b

Both fixes look right. Resetting pto_count in the space-discard path matches RFC 9002 A.11 (OnPacketNumberSpaceDiscarded), and switching the idle timer on State::Handshake and re-arming it on Established is a clean fix for the 2997ms deadline. Both tests target the exact failure timings. No blocking issues.

Non-blocking

  1. The default handshake timeout changes for every user, not only short-idle configs (connection/mod.rs reset_idle_timeout). Before, a handshake inherited max_idle_timeout. Now it's always handshake_idle_timeout (10s). That means:
    • With the default 30s idle timeout, handshakes now give up at 10s instead of 30s. That's a real behavior change for high-loss or slow links.
    • With max_idle_timeout(None) (idle disabled), the handshake used to have no idle deadline and now has 10s.
      If that's intended (msquic parity), say so in the PR and changelog. Otherwise consider min(handshake_idle_timeout, idle) only when the configured idle timeout is shorter, or making it Option<Duration> so None keeps the old inheritance.
  2. The negotiated value isn't used during the handshake on purpose. The server's transport params can lower the idle timeout before Established, but that's ignored until completion. That's fine, but a peer that advertises 1s still waits 10s on a dead handshake. It's worth a line in the handshake_idle_timeout doc.
  3. Test gaps. No test pins the Established swap, meaning that after connecting, the idle deadline is the negotiated 2s and not the leftover 10s. A short assert on idle close timing after connect would catch a regression if the reset_idle_timeout call in the Established arm gets moved.
  4. The fix is gated on downstream PRs. The drills only benefit after fix(proto): reset the PTO backoff when discarding Initial or Handshake keys noq#32 and Implement (forked) moq-transport-00 #34 land and the pin is bumped, as the quest notes. Keep the moq-quic and noq copies in sync, including the default for item 1.

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
(Written by Grok)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 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-10T04:53:05.031453Z 64f4ffd Manual request
ℹ️ 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.

… 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>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Re the automated review of db8d267b, addressed in 6d228d0b:

  1. Default handshake timeout for every config. This is intended: it's msquic parity, and the user picked this option. The PR description's Impact section covers the 30s → 10s case. The doc on handshake_idle_timeout now says it applies whether the idle timeout is shorter, longer, or disabled. I'm not adding an Option: a handshake with no deadline can hang forever.
  2. Peer-advertised idle timeout during the handshake. Now documented as well ("whatever idle timeout the peer advertises").
  3. Test of the Established swap. I added established_idles_out_at_negotiated_timeout. A client with a 2s idle timeout hears nothing after it is established and has to idle out within 3s. It fails if the established state keeps the 10s handshake timeout. It can't pin the explicit re-arm at Established specifically, because the first ack-eliciting packet the client sends afterwards (its Finished) re-arms the timer anyway. That line is defensive.
  4. Keeping noq in sync. feat(proto): add a handshake idle timeout noq#34 carries the same semantics and default. Another agent owns it along with the release and pin bump; I'll pass along the doc wording.

@codex review

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up review at 6d228d0b (previous: db8d267b)

This push adds established_idles_out_at_negotiated_timeout and expands the handshake_idle_timeout doc comment.

Earlier findings

  • Fixed: "no test of the Established swap." The new test sets a 2s idle timeout, completes the handshake, drops all inbound to the client, and asserts it closes in under 3s. That is well below the 10s handshake timeout, so it really pins the swap.
  • Addressed by documentation: the 10s default now says it applies whether the idle timeout is shorter, longer, or disabled. That makes the behavior change intentional. It's still worth a line in the PR body, since max_idle_timeout(None) users now get a 10s handshake limit.

Non-blocking

  • tests/mod.rs new test: only the upper bound is asserted. Adding elapsed >= Duration::from_secs(2) (minus a small slack) would also catch a regression where the connection closes early for some other reason, such as a PTO-driven close.
  • If client.next_wakeup() returns None while step() is false, the close loop spins forever. A bounded iteration count or a panic! on None would turn a hang into a failure.

Verdict: MERGE once CI is green.

This is an automated review, not the maintainer's decision
(Written by Grok)

@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: 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".

Comment on lines +110 to +113
/// 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

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 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 👍 / 👎.

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.

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>
@kixelated

Copy link
Copy Markdown
Collaborator Author

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 5e4ce04d.

@codex review

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up review after push 6d228d0b → 5e4ce04d

New: a deferred accept (Endpoint::accept stale-initial check, rs/moq-quic/src/endpoint.rs ~L602) now uses handshake_idle_timeout instead of max_idle_timeout, plus a deferred_accept_uses_handshake_idle_timeout test. That's the right fix: a 2s idle timeout no longer drops Initials the app is still deciding on.

Earlier findings:

  • Fixed: established_idles_out_at_negotiated_timeout now has a lower bound (2–3s), and a missing next_wakeup on an open connection panics through expect instead of looping forever.
  • Still open (non-blocking): the 10s handshake limit is now a hard cap in one more place.

Non-blocking:

  • Behavior change: before this, with max_idle_timeout = None a deferred Initial never went stale. Now it's always abandoned after handshake_idle_timeout. That's probably what you want, but it's a semantic change for servers that disable idle timeouts and hold incomings for a long time. Worth a line in the PR body or changelog.
  • If handshake_idle_timeout is set shorter than max_idle_timeout, deferred accepts now expire sooner than before. Using max(handshake, idle) would keep the old behavior as a floor, if that matters.
  • In the new test, the try_accept(late, …) at 10s sits exactly on the boundary (<= now). Fine as written, but a 10s + ε / 9s pair would also pin the edge.

CI: not checked green yet on this head.

Verdict: MERGE once CI is green (reviewed head 5e4ce04d)

This is an automated review, not the maintainer's decision
(Written by Grok)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 5e4ce04dd4

ℹ️ 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 and others added 2 commits October 9, 2026 21:49
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
@kixelated

Copy link
Copy Markdown
Collaborator Author

Ported moq-dev/noq#34's review fixes in 37be2e80:

  • The stale-Initial check uses handshake_idle_timeout. This was already in 5e4ce04d; it now uses checked_add.
  • An idle deadline too far out to represent never fires instead of panicking. New test: unbounded_handshake_idle_timeout, which panics without the change.
  • The probe-interval cap has no counterpart here. Quinn's PTO backoff has no idle-based cap, so there is nothing for the handshake idle timeout to pick. Noted in the README.

I also merged origin/main. Its Quest check currently fails on main itself, from dangling links in quest/m1/js-restart-keeps-request.md and quest/m1/webgpu-safari.md; it isn't a required check.

@codex review

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up review after push 5e4ce04d → 64f4ffde

Apart from a merge of main, the only new PR commit is 37be2e8, which ports noq#34's overflow fixes. A Duration::MAX handshake idle timeout no longer panics on Instant + Duration. In reset_idle_timeout (rs/moq-quic/src/connection/mod.rs ~L2124), checked_add now stops the timer instead. In the stale-Initial check in Endpoint::accept (rs/moq-quic/src/endpoint.rs ~L603), checked_add(...).is_some_and(...) now treats an unrepresentable deadline as never stale. Both are correct, and the README explains why noq#34's PTO-cap change doesn't apply here.

Non-blocking

  • The new unbounded_handshake_idle_timeout test only covers the idle-timer path. Pair::connect_with accepts right away, so the deferred-accept stale-Initial branch, which was the other panic site, never runs. A variant that delays accept (like the existing stale-initial test) with Duration::MAX would pin that fix too.
  • The test only checks that nothing panics. It would be stronger to also assert that the connection is established, or that Timer::Idle is unset while handshaking.
  • Still open from the earlier reviews: the Established-swap test has no lower bound, and I haven't checked the possible spin if next_wakeup is None.
  • CI (Check, Test, Windows, macOS) is still pending on this head.

Verdict: MERGE once CI is green.

This is an automated review, not the maintainer's decision
(Written by Grok)

@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: 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.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 64f4ffde12

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

main's quest check fails since #5141 and #5138 deleted the quests these name. Same change as #5173, so the two merge cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up review after push 6d228d0b → d5154670 (head d51546708c8b6ec34de7b1c9279eb2c781a0bf28), ignoring the main merge.

What changed:

  • Endpoint::accept (rs/moq-quic/src/endpoint.rs ~L600) now drops a stale waiting Initial at handshake_idle_timeout instead of max_idle_timeout, with deferred_accept_uses_handshake_idle_timeout covering both sides (accept at 3s ok, at 10s TimedOut).
  • reset_idle_timeout and the stale-accept check use checked_add, so a Duration::MAX timeout never fires instead of panicking; new unbounded_handshake_idle_timeout test.
  • Quest files drop two Required links whose blockers merged.

Earlier findings:

  • Fixed: established_idles_out_at_negotiated_timeout now asserts 2s..3s (lower bound added).
  • Fixed: the loop no longer spins when next_wakeup() is None; it expects a timer only while the connection is open.
  • Still true, now documented: the 10s handshake idle limit applies even with idle disabled, and that now extends to deferred accepts too.

Non-blocking:

  • Behavior change in accept: a server with max_idle_timeout(None) used to hold a waiting Initial indefinitely; it is now dropped after 10s. Sensible, but worth a line in the CHANGELOG/README note since it's a quiet change for apps that defer accept.
  • unbounded_handshake_idle_timeout only proves no panic during connect; it doesn't cover the endpoint.rs checked_add path (no deferred accept). A Wait + try_accept variant would exercise it.
  • CI still pending.

Verdict: MERGE once CI is green.

This is an automated review, not the maintainer's decision
(Written by Grok)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary at d51546708c8b6ec34de7b1c9279eb2c781a0bf28:

  • Cause 1: moq-quic resets pto_count when keys are discarded (RFC 9002 A.11).
  • Cause 2: TransportConfig::handshake_idle_timeout (10s by default) governs the idle timer and the stale-Initial check until the connection is established. After that the negotiated idle timeout takes over.
  • Overflow: a deadline that overflows Instant never fires.
  • Ported from noq#34: the review fixes. Its probe-interval cap doesn't apply, since quinn has no idle-based cap.
  • Quest files: dropped two Required links to blockers that already merged, matching test(media): open each window once playback starts, and prove late join can fail #5173, so main's Quest check passes again.
  • Decisions: as recorded in the PR description. The user picked the handshake idle timeout, plus a stacked noq PR for the drills.
  • Reviews: Codex found no issues at 64f4ffde, plus a 👍. The only later commit is the trivial quest link fix. Grok's last two non-blocking nits are deferred: a README line about max_idle_timeout(None) servers now dropping deferred Initials after 10s, and a deferred-accept Duration::MAX test.
  • CI: Check, Test, Quest, Windows and macOS all pass.

The quest stays open for the noq release and pin bump, which another agent owns.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge (squash) October 10, 2026 05:13
@kixelated
kixelated merged commit 4c9628e into main Oct 10, 2026
7 checks passed
@kixelated
kixelated deleted the quest/m1/test-flakes-2/impaired-handshake branch October 10, 2026 05:14
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