Repository navigation
Keep the admitted session when a superseded control owner's dial lands - #15197
Conversation
An RPC client generation closed while its dial is in flight never used the control lane, yet when the dial lands it retires the freshly admitted connection. Fails on this commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a newer RPC client generation replaced one whose dial was still in flight, the old transport found itself closed once the dial landed and retired the freshly admitted QUIC connection with explicit-redial. The replacement owner then saw control-lane-busy and dialed again, so every connect paid for two handshakes, and after a Mac stall the extra cycle cost about 17 s. That owner never read or wrote the control lane, so no EOF reached the Mac and the session is intact. It now only releases its claim (retiresConnection false) and leaves the session to the owner that replaced it. Owners that used the lane still retire the session on close, as #12324 requires, and revocation or scope changes close the engine directly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The server task dropped its IrxConnection right after writing the admit, and closing the QUIC connection could discard the admit before the client read it (ConnectionLost ApplicationClosed). The task now returns the connection and the test closes both ends after the assertions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
All contributors have signed the CLA ✍️ ✅ |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughWhen a control transport closes during establishment, it records the established connection and releases its owner claim without retiring the connection. The connect still throws ChangesControl transport owner release
Legacy admission test connection lifetime
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The replacement owner’s session is protected from the superseded owner’s release. The change is mergeable after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The replacement client can reuse the admitted session without bypassing admission. Owner-specific cleanup and revocation controls remain in place. The behavior when a client closes during connection setup without a replacement is less directly covered. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 24 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (24 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
|
Dogfood build of cmux DEV pr-15197-36bfbe28.app The link opens this exact commit in the cmux dev menu bar app. The build starts on each push and the page waits until it is ready; a newer push replaces it. It signs in against production, so Cloud or backend changes still need a tagged build with a development backend. |
|
Review (subagent, correctness-first; I re-verified the load-bearing claims against the diff) Holding this one. The fix is right for the ordering it covers, but the mirror-image ordering still runs the destructive teardown this PR exists to remove. Finding 1 (blocking): only one side of the race is fixed. let retiresConnection = !controlTerminationObserved && !connectionWasAlreadyClosedis true precisely because this owner never read or wrote the lane. So it retires the shared admitted connection and calls The production diff is a single hunk at Finding 2: the tests only cover the fixed ordering. Checked and clean:
Fixed: nothing. The gap is a behavior choice about where the "did this owner touch the lane" decision belongs, which is yours to make rather than something I should push onto your branch. Left: findings 1 and 2, for you. Ping me when it is updated and I will re-review and merge. Nice catch on the supersede path in the first place :) |
teamleaderleo
left a comment
There was a problem hiding this comment.
Reviewed the focused transport regression and its test coverage. Required checks are green; approving.
|
Merge receipt for |
9b0d37a fix: preserve SQL highlighting with Jinja templates (manaflow-ai#15634) 5850596 Use measured account-wide load in CI pickers (manaflow-ai#15609) 38e56ec docs: make CI runner policy the fleet routing owner (manaflow-ai#15638) ab564a4 Keep the admitted session when a superseded control owner's dial lands (manaflow-ai#15197) # Conflicts: # .github/workflows/ci-macos.yml # .github/workflows/ci.yml
Summary
Every iOS connect to a Mac paid for two QUIC handshakes, and a reconnect after the Mac stalled took about 17 s longer than it needed to. The phone journal shows the pattern on every fresh launch: the first session becomes ready and is retired with
explicit-redialin the same millisecond, next toclient-runtime control-lane-busy, and a second dial follows.The cause is in
IrxControlByteTransport. When a newer RPC client generation replaces one whose dial is still in flight, the old transport is closed; when its dial lands it finds itself closed and callscloseEstablishedPair, which retires the freshly admitted connection. The replacement owner's claim is refused while that happens (control-lane-busy), so it dials again.That owner never read or wrote the control lane, so no EOF reached the Mac and the session is intact. It now only releases its claim (
onClosewithretiresConnection: false) and leaves the session to the owner that replaced it. Owners that did use the lane still retire the session on close, which is the rule #12324 introduced because the Mac treats control-stream EOF as session termination. Revocation and scope changes already close the engine directly. #11891 described the intended behavior the same way: a closing RPC client "releases only its own Iroh claims instead of forcing a QUIC session replacement… including close-during-connect".Also fixes a flaky test from #14295:
legacy hello without the barrier capability admits immediatelydropped the server connection right after writing the admit, and the QUIC close could discard the admit before the client read it.Testing
IrxControlSupersededOwnerTests(new, live QUIC loopback) is committed first (a520084) and fails there: the superseded owner retires the connection and the replacement owner cannot use it. After the fix (0ece0aa) the claim is released without retiring, the connection stays open, and a replacement transport sends bytes that the server reads.closing while establishment is in flight releases the owner onceinIrxLiveQUICTestsis updated to the new semantics (released once, not retired, connection open).swift testinPackages/Shared/CmuxIrxTransport: 216 tests; the NAT barrier suite passed 6 of 6 repeated runs after the flake fix. One of five full runs hitIrxLivenessTests/nativePeerDeathIsDeferredInBackgroundAndRecoveredOnForeground(elapsed 3.0 s against a 2 s bound) on a machine with a load average above 500; that test does not touch this path.scripts/check-test-determinism.py: 0 findings.atstr(journal evidence above). Not yet rebuilt into that soak; the phone-side connect is the next thing to check live.Changelog
Fixed: The iOS app no longer connects twice when it opens a connection to a Mac, which also shortens reconnects after the Mac was briefly unreachable
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes every iOS connect to a Mac paying for two QUIC handshakes. When a newer RPC client generation replaces one whose dial is still in flight, the closed transport now releases its claim (
retiresConnection: false) instead of retiring the freshly admitted session, so the replacement owner no longer hitscontrol-lane-busyand dials again. Owners that actually used the control lane still retire the session on close; revocation and scope changes still close the engine directly.IrxNatBarrierTestscase that dropped the server connection before the client read the admit.Written for commit 36bfbe2. Summary will update on new commits.
Summary by CodeRabbit