From f1bc87b1227295ca1d8ff1413472e79ca3859f73 Mon Sep 17 00:00:00 2001 From: oguzhanmeteozturk Date: Wed, 12 Aug 2026 03:57:28 -0500 Subject: [PATCH] fix(dist): close piped stdin before waiting on the child `CommandExt::check_piped` moves the child's stdin out of the `Child` with `.take()`, binding it to a local that lives until the end of the function. `Child::wait_with_output` closes only the stdin still held by the `Child` (`drop(self.stdin.take())`), which is `None` here, so that is a no-op and the write end of the pipe stays open while the parent waits for the child to exit. The closure cannot close it either: its parameter is `&mut ChildStdin`, so the callee has no way to drop the value. Only `check_piped` can. Both callers are in `DockerBuilder` and pipe a tar into `docker cp - :/`, which reads until EOF. With the write end held open it never exits, so every job on a `type = "docker"` build server blocks indefinitely with its container left in `Created` and the client falls back to local compilation. Both call sites already drop the corresponding reader immediately after the call (`drop(toolchain_rdr)`, `drop(inputs_rdr)`); this makes the writer symmetric. --- src/bin/sccache-dist/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bin/sccache-dist/build.rs b/src/bin/sccache-dist/build.rs index bfd7a980e5..6d0fdd2100 100644 --- a/src/bin/sccache-dist/build.rs +++ b/src/bin/sccache-dist/build.rs @@ -55,6 +55,11 @@ impl CommandExt for Command { .take() .expect("Requested piped stdin but not present"); pipe(&mut stdin).context("Failed to pipe input to process")?; + // `process.stdin` was moved out by the `.take()` above, so + // `wait_with_output()` has no stdin left to close. Without this the write + // end stays open for the rest of the function and a child that reads to + // EOF never returns. + drop(stdin); let output = process .wait_with_output() .context("Failed to wait for process to return")?;