[WIP] fix(minimal2): serialize concurrent attach establishment (#588) - #590
[WIP] fix(minimal2): serialize concurrent attach establishment (#588)#590norrietaylor wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughAdds a hidden ChangesAttach Lock Serialization
Sequence Diagram(s)sequenceDiagram
participant CLI as minimal CLI (attach)
participant Helper as attach-lock helper process
participant LockFile as Lock file (flock)
participant SSH as ssh
CLI->>Helper: spawn "attach-lock" with lock_path, window_ms
Helper->>LockFile: libc::flock(LOCK_EX)
LockFile-->>Helper: lock acquired
Helper-->>CLI: print "ok" to stdout
CLI->>SSH: exec ssh
Helper->>Helper: sleep(window_ms)
Helper->>LockFile: release lock on exit
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
aa6b77a to
4881f18
Compare
Concurrent own-ip session SSH handshakes racing through libkrun's host->guest vsock muxer can leave one session wedged (its RequestShell is never delivered to session_host), so an overlapping `minimal attach` occasionally hangs. The defect is a rare timing race in libkrun's vsock device under concurrent connections (~0.4%/attach at high concurrency); this is an in-repo workaround pending the upstream fix. `attach` now spawns a short-lived detached `attach-lock` helper that holds an exclusive `flock` for a bounded establishment window (default 4000ms, MINIMAL_ATTACH_ESTABLISH_WINDOW_MS) before exec-ing ssh. A concurrent attach blocks on the same lock and starts its handshake ~one window later, so the racy handshake/RequestShell phase is serialized instead of overlapping. Uncontended single attaches only wait for the lock-acquired signal (~0.24s), so steady-state latency is unchanged. Uses blocking `flock(LOCK_EX)` -- the same primitive lcache's read-tracker uses, minus LOCK_NB so a contender waits rather than failing -- which works on Darwin (BSD) and Linux alike; no new crate dependency beyond libc. Tracked upstream in #588. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4881f18 to
89883b2
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/minimal/src/main.rs`:
- Around line 623-635: The attach-lock flow in the helper spawn block can hang
forever on `out.read(&mut buf)` and leaves the spawned child unreaped. Update
the attach path around the `cmd_attach_lock`/`child.stdout.take()` logic to use
a bounded wait or timeout and fall back to the unserialized path if the helper
does not report readiness promptly. Also keep a handle to the spawned child and
explicitly reap it after `ssh.exec()` (or when attach ends) so it cannot become
a zombie.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 080fe7d5-d740-4cb4-9699-b54123184660
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
crates/minimal/Cargo.tomlcrates/minimal/src/main.rs
✅ Files skipped from review due to trivial changes (1)
- crates/minimal/Cargo.toml
| Ok(mut child) => { | ||
| // Block until the holder reports `ok` (lock acquired) or dies. | ||
| if let Some(mut out) = child.stdout.take() { | ||
| use std::io::Read; | ||
| let mut buf = [0u8; 3]; | ||
| let _ = out.read(&mut buf); | ||
| } | ||
| // Leave the holder running detached; it releases after the window. | ||
| } | ||
| Err(e) => { | ||
| tracing::warn!("attach-lock helper failed to spawn ({e}); attaching unserialized"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map relevant file structure first.
ast-grep outline crates/minimal/src/main.rs --view expanded > /tmp/main_outline.txt || true
# Show the relevant line ranges around the attach-lock flow.
sed -n '580,740p' crates/minimal/src/main.rs | cat -n
echo
echo '--- outline ---'
sed -n '1,240p' /tmp/main_outline.txt | cat -nRepository: gominimal/minimal
Length of output: 11405
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the attach-lock helper / flock logic and any timeout or fallback handling.
rg -n -C 4 "attach-lock|flock|LOCK_EX|proceed unserialized|spawn\\(|stdout\\.take\\(|read\\(&mut buf\\)|cmd_attach_lock|unserialized" crates/minimal/src/main.rs
echo
echo '--- any wait/reap handling around detached child ---'
rg -n -C 4 "wait\\(|try_wait\\(|kill\\(|detach|zombie|reap" crates/minimal/src/main.rsRepository: gominimal/minimal
Length of output: 5911
Bound the attach-lock wait and reap the helper
out.read(&mut buf)can block indefinitely here becausecmd_attach_lockuses a blockingflock(LOCK_EX)with no timeout; a stale holder on the lock file will stall attach instead of degrading to the unserialized path.- The spawned helper is never waited on before
ssh.exec(), so if it exits during the session it can sit as a zombie until the parent process exits.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/minimal/src/main.rs` around lines 623 - 635, The attach-lock flow in
the helper spawn block can hang forever on `out.read(&mut buf)` and leaves the
spawned child unreaped. Update the attach path around the
`cmd_attach_lock`/`child.stdout.take()` logic to use a bounded wait or timeout
and fall back to the unserialized path if the helper does not report readiness
promptly. Also keep a handle to the spawned child and explicitly reap it after
`ssh.exec()` (or when attach ends) so it cannot become a zombie.
Mitigation for #588: a second overlapping
minimal2 attachto an own-ip session wedges (no shell).Stacked on #581 (base branch
feat/networking-host-exposure) because it depends on that PR's own-ip attach path.Root cause
Two own-ip session SSH handshakes racing through libkrun's host→guest virtio-vsock muxer leave one session wedged — its
RequestShellis never delivered tosession_host, so the second concurrent attach hangs. Verified the defect is in libkrun's vsock device under concurrent connections (nottokio-vsock, which is correct; not minimald logic). libkrun 1.19.0 fixed only the sequential direct-vsock case. Full investigation in #588.Fix (in-repo workaround, pending the upstream libkrun fix)
minimal2 attachspawns a short-lived detachedattach-lockhelper that holds an exclusiveflockfor a bounded establishment window (default 4000ms,MINIMAL_ATTACH_ESTABLISH_WINDOW_MS) before exec-ing ssh:RequestShellphase is serialized instead of overlapping the muxer.exec sshis preserved (clean interactive TTY); the helper holds the lock in the background and releases on window expiry.Verification (macOS/HVF, DM1)
RequestShell+attached OwnIp PTask, with session B attaching ~4s after A (the flock window).cargo fmt/cargo clippy -p minimal2 --all-targets -D warnings/cargo test -p minimal2clean.Notes
docs/specs/03-spec-networking/test-plan.shTC2 same-host peer) without waiting on the upstream libkrun fix.References: #588
🤖 Generated with Claude Code
Summary by CodeRabbit