Skip to content

Commit 983e200

Browse files
committed
Auto merge of rust-lang#131155 - jieyouxu:always-kill, r=<try>
Prevent building cargo from invalidating build cache of other tools due to conditionally applied `-Zon-broken-pipe=kill` via tracked `RUSTFLAGS` This PR fixes rust-lang#130980 where building cargo invalidated the tool build caches of other tools (such as rustdoc) because `-Zon-broken-pipe=kill` was conditionally passed via `RUSTFLAGS` for other tools *except* for cargo. The differing `RUSTFLAGS` triggered tool build cache invalidation as `RUSTFLAGS` is a tracked env var -- any changes in `RUSTFLAGS` requires a rebuild. `-Zon-broken-pipe=kill` is load-bearing for rustc and rustdoc to not ICE on broken pipes due to usages of raw std `println!` that panics without the flag being set, which manifests in ICEs. I can't say I like the changes here, but it is what it is... See detailed discussions and history of `-Zon-broken-pipe=kill` usage in https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F/near/474593815. ## Approach This PR fixes the tool build cache invalidation by informing the `rustc` binary shim when to apply `-Zon-broken-pipe=kill` (i.e. when the rustc binary shim is not used to build cargo). This information is not communicated by `RUSTFLAGS`, which is an env var tracked by cargo, and instead uses an untracked env var `UNTRACKED_BROKEN_PIPE_FLAG` so we won't trigger tool build cache invalidation. We preserve bootstrap's behavior of not setting that flag for cargo by conditionally omitting setting `UNTRACKED_BROKEN_PIPE_FLAG` when building cargo. Notably, the `-Zon-broken-pipe=kill` instance in https://github.com/rust-lang/rust/blob/1e5719bdc40bb553089ce83525f07dfe0b2e71e9/src/bootstrap/src/core/build_steps/compile.rs#L1058 is not modified because that is used to build rustc only and not cargo itself. Thanks to `@cuviper` for the idea! ## Testing ### Integration testing This PR introduces a run-make test for rustc and rustdoc that checks that when they do not ICE/panic when they encounter a broken pipe of the stdout stream. I checked this test will catch the broken pipe ICE regression for rustc on Linux (at least) by commenting out https://github.com/rust-lang/rust/blob/1e5719bdc40bb553089ce83525f07dfe0b2e71e9/src/bootstrap/src/core/build_steps/compile.rs#L1058, and the test failed because rustc ICE'd. ### Manual testing I have manually tried: 1. `./x clean && `./x test build --stage 1` -> `rustc +stage1 --print=sysroot | false`: no ICE. 2. `./x clean` -> `./x test run-make` twice: no stage 1 cargo rebuilds. 3. `./x clean` -> `./x build rustdoc` -> `rustdoc +stage1 --version | false`: no panics. 4. `./x test src/tools/cargo`: tests pass, notably `build::close_output` and `cargo_command::closed_output_ok` do not fail which would fail if cargo was built with `-Zon-broken-pipe=kill`. ## Related discussions Thanks to everyone who helped! - https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Applying.20.60-Zon-broken-pipe.3Dkill.60.20flags.20in.20bootstrap.3F - https://rust-lang.zulipchat.com/#narrow/stream/326414-t-infra.2Fbootstrap/topic/Modifying.20run-make.20tests.20unnecessarily.20rebuild.20stage.201.20.2E.2E.2E - https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F Fixes rust-lang#130980 Closes rust-lang#131059 --- try-job: aarch64-apple try-job: x86_64-msvc try-job: x86_64-mingw
2 parents 7caad69 + b20436b commit 983e200

File tree

4 files changed

+83
-6
lines changed

4 files changed

+83
-6
lines changed

src/bootstrap/src/bin/rustc.rs

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ fn main() {
136136
cmd.args(lint_flags.split_whitespace());
137137
}
138138

139+
// Conditionally pass `-Zon-broken-pipe=kill` to underlying rustc. Not all binaries want
140+
// `-Zon-broken-pipe=kill`, which includes cargo itself.
141+
if env::var_os("FORCE_ON_BROKEN_PIPE_KILL").is_some() {
142+
cmd.arg("-Z").arg("on-broken-pipe=kill");
143+
}
144+
139145
if target.is_some() {
140146
// The stage0 compiler has a special sysroot distinct from what we
141147
// actually downloaded, so we just always pass the `--sysroot` option,

src/bootstrap/src/core/build_steps/compile.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1053,8 +1053,19 @@ pub fn rustc_cargo(
10531053

10541054
cargo.rustdocflag("-Zcrate-attr=warn(rust_2018_idioms)");
10551055

1056-
// If the rustc output is piped to e.g. `head -n1` we want the process to be
1057-
// killed, rather than having an error bubble up and cause a panic.
1056+
// If the rustc output is piped to e.g. `head -n1` we want the process to be killed, rather than
1057+
// having an error bubble up and cause a panic.
1058+
//
1059+
// FIXME(jieyouxu): this flag is load-bearing for rustc to not ICE on broken pipes, because
1060+
// rustc internally sometimes uses std `println!` -- but std `println!` by default will panic on
1061+
// broken pipes, and uncaught panics will manifest as an ICE. The compiler *should* handle this
1062+
// properly, but this flag is set in the meantime to paper over the I/O errors.
1063+
//
1064+
// See <https://github.com/rust-lang/rust/issues/131059> for details.
1065+
//
1066+
// Also see the discussion for properly handling I/O errors related to broken pipes, i.e. safe
1067+
// variants of `println!` in
1068+
// <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F>.
10581069
cargo.rustflag("-Zon-broken-pipe=kill");
10591070

10601071
if builder.config.llvm_enzyme {

src/bootstrap/src/core/build_steps/tool.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,28 @@ pub fn prepare_tool_cargo(
209209
// See https://github.com/rust-lang/rust/issues/116538
210210
cargo.rustflag("-Zunstable-options");
211211

212-
// `-Zon-broken-pipe=kill` breaks cargo tests
212+
// NOTE: The root cause of needing `-Zon-broken-pipe=kill` in the first place is because `rustc`
213+
// and `rustdoc` doesn't gracefully handle I/O errors due to usages of raw std `println!` macros
214+
// which panics upon encountering broken pipes. `-Zon-broken-pipe=kill` just papers over that
215+
// and stops rustc/rustdoc ICEing on e.g. `rustc --print=sysroot | false`.
216+
//
217+
// cargo explicitly does not want the `-Zon-broken-pipe=kill` paper because it does actually use
218+
// variants of `println!` that handles I/O errors gracefully. It's also a breaking change for a
219+
// spawn process not written in Rust, especially if the language default handler is not
220+
// `SIG_IGN`. Thankfully cargo tests will break if we do set the flag.
221+
//
222+
// For the cargo discussion, see
223+
// <https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Applying.20.60-Zon-broken-pipe.3Dkill.60.20flags.20in.20bootstrap.3F>.
224+
//
225+
// For the rustc discussion, see
226+
// <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F>
227+
// for proper solutions.
213228
if !path.ends_with("cargo") {
214-
// If the output is piped to e.g. `head -n1` we want the process to be killed,
215-
// rather than having an error bubble up and cause a panic.
216-
cargo.rustflag("-Zon-broken-pipe=kill");
229+
// Use an untracked env var `FORCE_ON_BROKEN_PIPE_KILL` here instead of `RUSTFLAGS`.
230+
// `RUSTFLAGS` is tracked by cargo. Conditionally omitting `-Zon-broken-pipe=kill` from
231+
// `RUSTFLAGS` causes unnecessary tool rebuilds due to cache invalidation from building e.g.
232+
// cargo *without* `-Zon-broken-pipe=kill` but then rustdoc *with* `-Zon-broken-pipe=kill`.
233+
cargo.env("FORCE_ON_BROKEN_PIPE_KILL", "-Zon-broken-pipe=kill");
217234
}
218235

219236
cargo
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! Check that `rustc` and `rustdoc` does not ICE upon encountering a broken pipe due to unhandled
2+
//! panics from raw std `println!` usages.
3+
//!
4+
//! Regression test for <https://github.com/rust-lang/rust/issues/34376>.
5+
6+
//@ ignore-cross-compile (needs to run test binary)
7+
8+
#![feature(anonymous_pipe)]
9+
10+
use std::io::Read;
11+
use std::os::unix::process::ExitStatusExt;
12+
use std::process::{Command, Stdio};
13+
14+
use run_make_support::{env_var, libc};
15+
16+
fn check_broken_pipe_handled_gracefully(name: &str, mut cmd: Command) {
17+
let (reader, writer) = std::pipe::pipe().unwrap();
18+
drop(reader); // close read-end
19+
cmd.stdout(writer).stderr(Stdio::piped());
20+
21+
let mut child = cmd.spawn().unwrap();
22+
23+
let mut stderr = String::new();
24+
child.stderr.as_mut().unwrap().read_to_string(&mut stderr).unwrap();
25+
let status = child.wait().unwrap();
26+
27+
assert!(!status.success(), "{name}");
28+
29+
const PANIC_ICE_EXIT_STATUS: i32 = 101;
30+
assert_ne!(status.code(), Some(101), "{name}");
31+
32+
assert!(stderr.is_empty(), "{name} stderr:\n{}", stderr);
33+
}
34+
35+
fn main() {
36+
let mut rustc = Command::new(env_var("RUSTC"));
37+
rustc.arg("--print=sysroot");
38+
check_broken_pipe_handled_gracefully("rustc", rustc);
39+
40+
let mut rustdoc = Command::new(env_var("RUSTDOC"));
41+
rustdoc.arg("--version");
42+
check_broken_pipe_handled_gracefully("rustdoc", rustdoc);
43+
}

0 commit comments

Comments
 (0)