fix(cli): read stdin on a dedicated OS thread so TTY exec exits without a stray ENTER#626
fix(cli): read stdin on a dedicated OS thread so TTY exec exits without a stray ENTER#626G4614 wants to merge 1 commit into
Conversation
e01f03f to
6048e91
Compare
…ut a stray ENTER tokio::io::stdin() parks its uncancellable read(2) on a tokio blocking-pool thread, which the runtime joins on shutdown. When the remote shell exits, aborting the async task cannot interrupt that parked read, so process exit hung until the user pressed ENTER. Move the blocking read onto a plain std::thread (not joined on shutdown) and forward bytes over a channel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The crux — the one-line cause and the change The interactive stdin reader used if let Some(h) = stdin_handle.as_ref() {
h.abort(); // cancels the async task — NOT the OS thread already parked in read(2)
}
Fix — // before
let mut stdin = tokio::io::stdin();
loop { match stdin.read(&mut buf).await { /* … */ } }
// after
let (tx, mut rx) = tokio::sync::mpsc::channel::<Vec<u8>>(16);
std::thread::spawn(move || {
let mut stdin = std::io::stdin();
loop {
match stdin.read(&mut buf) {
Ok(n) => { if tx.blocking_send(buf[..n].to_vec()).is_err() { break } }
/* … */
}
}
});
while let Some(chunk) = rx.recv().await { /* forward to the box */ }The process now exits promptly on shell exit; the still-parked read is reaped by process exit. Verified on a real box: host prompt returns in ~0.03 s after one |
Move CLI stdin reads off
tokio::io::stdin()onto a dedicatedstd::thread(not joined at runtime shutdown) soexec -tireturns to the host the moment the in-box shell exits, instead of hanging until a stray ENTER.Test plan
Two-sided (reverted vs applied) against a real box under a
pexpectPTY harness — typeexit↵ once, then wait for the host process to return.exit↵ once → does the host prompt come back? A correct CLI returns immediately; the buggy CLI hangs until a spurious 2nd ENTER unblocks the parked read./ #shown,echo $((6*7))→MARKER_42echoed back from inside the box, clean exit 0.exit↵)tokio::io::stdin())std::thread)Both sides ran on
main + #625, because TTY exec is dead on baremain(#625 / libcontainer-0.6check_terminal→invalid runtime spec) and the hang path is otherwise unreachable: the fix is orthogonal in code (CLIterminal/mod.rsvs #625's guest spec) but its user-visible benefit only lands once #625 does. No automated regression test is bundled — a faithful reproducer needs a live PTY + running box + #625; the manual two-sided PTY probe above is the verification.