Fix stdout/stderr ordering in ShutdownPrepCheck #112

Merged
osobh merged 1 commits from fix-shutdown-prep-check-stderr-ordering into main 2026-08-01 02:45:43 +00:00
+12 -4
View File
@@ -87,16 +87,24 @@ pub async fn check() -> Result<ShutdownPrepCheckReply> {
if !script.exists() { if !script.exists() {
bail!("shutdown-prep script not found at {}", script.display()); bail!("shutdown-prep script not found at {}", script.display());
} }
// `2>&1` inside the shell merges stderr into stdout *before*
// either stream is piped back to us, preserving true
// chronological order. Capturing stdout/stderr separately (as
// `Command::output()` does by default) and concatenating them
// after the fact loses interleaving entirely — every stderr line
// lands at the very end regardless of when it was actually
// printed, which makes a mid-script warning (e.g. "replicate not
// configured on this node") look like a failure that happened
// after "DRY RUN COMPLETE".
let run = Command::new("bash") let run = Command::new("bash")
.arg(&script) .arg("-c")
.arg("--dry-run") .arg(format!("{} --dry-run 2>&1", script.display()))
.output(); .output();
let output = timeout(CHECK_TIMEOUT, run) let output = timeout(CHECK_TIMEOUT, run)
.await .await
.context("shutdown-prep --dry-run timed out")? .context("shutdown-prep --dry-run timed out")?
.context("spawning shutdown-prep --dry-run")?; .context("spawning shutdown-prep --dry-run")?;
let mut combined = String::from_utf8_lossy(&output.stdout).into_owned(); let combined = String::from_utf8_lossy(&output.stdout).into_owned();
combined.push_str(&String::from_utf8_lossy(&output.stderr));
Ok(ShutdownPrepCheckReply { Ok(ShutdownPrepCheckReply {
ready: output.status.success(), ready: output.status.success(),
output: combined, output: combined,