Skip to content

Commit

Permalink
Fix deadlock when build scripts are waiting for input on stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
arlosi committed Oct 10, 2022
1 parent e691e18 commit 68118d4
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 12 deletions.
28 changes: 16 additions & 12 deletions crates/cargo-util/src/process_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub struct ProcessBuilder {
/// See [`ProcessBuilder::retry_with_argfile`] for more information.
retry_with_argfile: bool,
/// Data to write to stdin.
stdin: Vec<u8>,
stdin: Option<Vec<u8>>,
}

impl fmt::Display for ProcessBuilder {
Expand Down Expand Up @@ -82,7 +82,7 @@ impl ProcessBuilder {
jobserver: None,
display_env_vars: false,
retry_with_argfile: false,
stdin: Vec::new(),
stdin: None,
}
}

Expand Down Expand Up @@ -212,7 +212,7 @@ impl ProcessBuilder {

/// Sets a value that will be written to stdin of the process on launch.
pub fn stdin<T: Into<Vec<u8>>>(&mut self, stdin: T) -> &mut Self {
self.stdin = stdin.into();
self.stdin = Some(stdin.into());
self
}

Expand Down Expand Up @@ -284,18 +284,22 @@ impl ProcessBuilder {
fn _output(&self) -> io::Result<Output> {
if !debug_force_argfile(self.retry_with_argfile) {
let mut cmd = self.build_command();
match piped(&mut cmd).spawn() {
match piped(&mut cmd, self.stdin.is_some()).spawn() {
Err(ref e) if self.should_retry_with_argfile(e) => {}
Err(e) => return Err(e),
Ok(mut child) => {
child.stdin.take().unwrap().write_all(&self.stdin)?;
if let Some(stdin) = &self.stdin {
child.stdin.take().unwrap().write_all(stdin)?;
}
return child.wait_with_output();
}
}
}
let (mut cmd, argfile) = self.build_command_with_argfile()?;
let mut child = piped(&mut cmd).spawn()?;
child.stdin.take().unwrap().write_all(&self.stdin)?;
let mut child = piped(&mut cmd, self.stdin.is_some()).spawn()?;
if let Some(stdin) = &self.stdin {
child.stdin.take().unwrap().write_all(stdin)?;
}
let output = child.wait_with_output();
close_tempfile_and_log_error(argfile);
output
Expand Down Expand Up @@ -340,14 +344,14 @@ impl ProcessBuilder {

let spawn = |mut cmd| {
if !debug_force_argfile(self.retry_with_argfile) {
match piped(&mut cmd).spawn() {
match piped(&mut cmd, false).spawn() {
Err(ref e) if self.should_retry_with_argfile(e) => {}
Err(e) => return Err(e),
Ok(child) => return Ok((child, None)),
}
}
let (mut cmd, argfile) = self.build_command_with_argfile()?;
Ok((piped(&mut cmd).spawn()?, Some(argfile)))
Ok((piped(&mut cmd, false).spawn()?, Some(argfile)))
};

let status = (|| {
Expand Down Expand Up @@ -541,11 +545,11 @@ fn debug_force_argfile(retry_enabled: bool) -> bool {
cfg!(debug_assertions) && env::var("__CARGO_TEST_FORCE_ARGFILE").is_ok() && retry_enabled
}

/// Creates new pipes for stderr, stdout and stdin.
fn piped(cmd: &mut Command) -> &mut Command {
/// Creates new pipes for stderr, stdout, and optionally stdin.
fn piped(cmd: &mut Command, pipe_stdin: bool) -> &mut Command {
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.stdin(if pipe_stdin { Stdio::piped() } else { Stdio::null() })
}

fn close_tempfile_and_log_error(file: NamedTempFile) {
Expand Down
26 changes: 26 additions & 0 deletions tests/testsuite/build_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4905,3 +4905,29 @@ for more information about build script outputs.
)
.run();
}

#[cargo_test]
fn custom_build_closes_stdin() {
// Ensure stdin is closed to prevent deadlock.
// See https://github.com/rust-lang/cargo/issues/11196
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.5.0"
build = "build.rs"
"#,
)
.file("src/main.rs", "fn main() {}")
.file(
"build.rs",
r#"fn main() {
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
}"#,
)
.build();
p.cargo("build").run();
}

0 comments on commit 68118d4

Please sign in to comment.