Bug
In src/tools/io.rs, execute_shell() reads stdout and stderr sequentially before applying the timeout to wait():
// Read ALL of stdout (blocks until EOF)ifletSome(stdout) = stdout {/* read all lines */}// Read ALL of stderr (blocks until EOF) ifletSome(stderr) = stderr {/* read all lines */}// THEN apply timeout to wait()let status = tokio::time::timeout(timeout_secs, child.0.wait()).await;Problems
Timeout doesn't cover I/O reads — If a command holds stdout open (e.g. tail -f, a hung process), reader.next_line().await blocks forever. The tokio::time::timeout is never reached.
Sequential reads can deadlock — If a process fills the stderr pipe buffer (~64KB) while the parent is still reading stdout, the child blocks on stderr write, the parent blocks on stdout read waiting for EOF → deadlock.
Fix
Wrap stdout, stderr reads and wait in a single tokio::join! under one timeout:
let result = tokio::time::timeout(Duration::from_secs(timeout_secs),async{let stdout_fut = async{/* read all stdout */};let stderr_fut = async{/* read all stderr */};let wait_fut = child.0.wait();
tokio::join!(stdout_fut, stderr_fut, wait_fut)}).await;Found during code review of the cancellation PR (#61).
Bug
In
src/tools/io.rs,execute_shell()reads stdout and stderr sequentially before applying the timeout towait():Problems
Timeout doesn't cover I/O reads — If a command holds stdout open (e.g.
tail -f, a hung process),reader.next_line().awaitblocks forever. Thetokio::time::timeoutis never reached.Sequential reads can deadlock — If a process fills the stderr pipe buffer (~64KB) while the parent is still reading stdout, the child blocks on stderr write, the parent blocks on stdout read waiting for EOF → deadlock.
Fix
Wrap stdout, stderr reads and wait in a single
tokio::join!under one timeout:Found during code review of the cancellation PR (#61).