-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rustc -C help
panics (ICEs) when piped to a closed stdout
#98700
Comments
@rustbot label +O-Windows |
This is a consequence of using the print series of macros. They ignore The panic could be handled in a global |
rustc explicitly chooses to override the startup code (which installs a SIGPIPE handler of SIG_IGN) and reinstall a handler of SIG_DFL: rust/compiler/rustc_driver/src/lib.rs Lines 433 to 440 in 7f08d04
This means that rustc halts via SIGPIPE on POSIXy platforms and a broken pipe. To match this behavior on non-POSIX platforms, rustc should probably avoid the What makes this interesting to me is that The Zulip thread about the SIGPIPE situation is highly relevant here. The current PR provides a platform-specific |
Oh, that is interesting and a bit of an odd difference. Should be easily fixed though? I mean if rustc already has code to print without ICEs. |
|
if matches.opt_present("h") || matches.opt_present("help") { | |
// Only show unstable options in --help if we accept unstable options. | |
let unstable_enabled = nightly_options::is_unstable_enabled(&matches); | |
let nightly_build = nightly_options::match_is_nightly_build(&matches); | |
usage(matches.opt_present("verbose"), unstable_enabled, nightly_build); | |
return None; | |
} |
rust/compiler/rustc_driver/src/lib.rs
Lines 759 to 791 in 7425fb2
fn usage(verbose: bool, include_unstable_options: bool, nightly_build: bool) { | |
let groups = if verbose { config::rustc_optgroups() } else { config::rustc_short_optgroups() }; | |
let mut options = getopts::Options::new(); | |
for option in groups.iter().filter(|x| include_unstable_options || x.is_stable()) { | |
(option.apply)(&mut options); | |
} | |
let message = "Usage: rustc [OPTIONS] INPUT"; | |
let nightly_help = if nightly_build { | |
"\n -Z help Print unstable compiler options" | |
} else { | |
"" | |
}; | |
let verbose_help = if verbose { | |
"" | |
} else { | |
"\n --help -v Print the full set of options rustc accepts" | |
}; | |
let at_path = if verbose { | |
" @path Read newline separated options from `path`\n" | |
} else { | |
"" | |
}; | |
println!( | |
"{options}{at_path}\nAdditional help: | |
-C help Print codegen options | |
-W help \ | |
Print 'lint' options and default settings{nightly}{verbose}\n", | |
options = options.usage(message), | |
at_path = at_path, | |
nightly = nightly_help, | |
verbose = verbose_help | |
); | |
} |
-C help
rust/compiler/rustc_driver/src/lib.rs
Lines 1042 to 1047 in 7425fb2
let cg_flags = matches.opt_strs("C"); | |
if cg_flags.iter().any(|x| *x == "help") { | |
describe_codegen_flags(); | |
return None; | |
} |
rust/compiler/rustc_driver/src/lib.rs
Lines 931 to 932 in 7425fb2
println!("\nAvailable codegen options:\n"); | |
print_flag_list("-C", config::CG_OPTIONS); |
rust/compiler/rustc_driver/src/lib.rs
Lines 935 to 950 in 7425fb2
pub fn print_flag_list<T>( | |
cmdline_opt: &str, | |
flag_list: &[(&'static str, T, &'static str, &'static str)], | |
) { | |
let max_len = flag_list.iter().map(|&(name, _, _, _)| name.chars().count()).max().unwrap_or(0); | |
for &(name, _, _, desc) in flag_list { | |
println!( | |
" {} {:>width$}=val -- {}", | |
cmdline_opt, | |
name.replace('_', "-"), | |
desc, | |
width = max_len | |
); | |
} | |
} |
There's nothing different going on here; println!
is used in both paths.
Instead, what I think is happening is buffering. I wrote a little test program:
fn main() {
for i in 0.. {
dbg!(i);
println!("Hello, world!");
}
}
And running it I get
(All runs here are done in the Windows Terminal terminal emulator with a shell of nushell, which as of 0.64.0 is using cmd to run non-builtin commands. I did a smaller number of tests directly in cmd when I realized that this may be impacting the results, and observed equivalent behavior.)
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The number of dbg!
s I get is variable, though: I've seen as low as i = 10
and as high as i = 19
. (I even saw i = 26
with this-command-does-not-exist
in nushell...)
Some other runs with interesting qualities
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
❯ ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 14
[src\main.rs:3] i = 15
[src\main.rs:3] i = 16
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 14
[src\main.rs:3] i = 15
[src\main.rs:3] i = 18
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 14
[src\main.rs:3] i = 15
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
[src\main.rs:3] i = 18
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
❯ cargo build --release; ^'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 14
[src\main.rs:3] i = 15
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
[src\main.rs:3] i = 19
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
These commands were run directly into a cmd shell, still with Windows Terminal as the terminal emulator:
D:\git\cad97\playground>cargo build --release && "D:\.rust\target\release\playground.exe" | cmd /c "exit"
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
D:\git\cad97\playground>cargo build --release && "D:\.rust\target\release\playground.exe" | cmd /c "exit"
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
D:\git\cad97\playground>cargo build --release && "D:\.rust\target\release\playground.exe" | cmd /c "exit"
Finished release [optimized] target(s) in 0.00s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 14
thread 'main' panicked at 'failed printing to stdout: The pipe is being closed. (os error 232)', library\std\src\io\stdio.rs:1015:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
In the limited trials I did, I didn't see any skipped iterations directly in cmd.
Something interesting is going on here: if you'll note, the highlighted example is missing i = 14
and i = 15
, despite stderr locking saying that this shouldn't be possible. This isn't a one-off error or just copy/paste error, either; this kind of data loss happened multiple times (see above dropdown for some cherry-picked trial runs).
In any case, changing the loop to 0..10
results in a quiet exit every time. The reason rustc --help
exits quietly thus seems to be not because that code path is handling ErrorKind::BrokenPipe
, but because Windows isn't returning a broken pipe error quickly.
Now, I realized after collecting most of the above data that the shell may be impacting the result... Observed differences:
- nushell turns
^'D:\.rust\target\release\playground.exe' | extern-command
into roughlycmd /c "D:\.rust\target\release\playground.exe" | cmd /c extern-command
. This is why^'D:\.rust\target\release\playground.exe' | this-command-does-not-exist
results in a broken pipe. - cmd and pwsh parse and do command lookup before executing the first part of a pipeline. This means e.g.
&'D:\.rust\target\release\playground.exe' | this-command-does-not-exist
pwsh fails before callingplayground.exe
at all. - pwsh is really an odd one out here...
❯ cargo build --release; &'D:\.rust\target\release\playground.exe' | cmd /c 'exit'
Compiling playground v0.1.0 (D:\git\cad97\playground)
Finished release [optimized] target(s) in 0.46s
[src\main.rs:3] i = 0
[src\main.rs:3] i = 1
[src\main.rs:3] i = 2
[src\main.rs:3] i = 3
[src\main.rs:3] i = 4
[src\main.rs:3] i = 5
[src\main.rs:3] i = 6
[src\main.rs:3] i = 7
[src\main.rs:3] i = 8
[src\main.rs:3] i = 9
[src\main.rs:3] i = 10
[src\main.rs:3] i = 11
[src\main.rs:3] i = 12
[src\main.rs:3] i = 13
[src\main.rs:3] i = 14
[src\main.rs:3] i = 15
[src\main.rs:3] i = 16
[src\main.rs:3] i = 17
[src\main.rs:3] i = 18
[src\main.rs:3] i = 19
[src\main.rs:3] i = 20
[src\main.rs:3] i = 21
[src\main.rs:3] i = 22
[src\main.rs:3] i = 23
[src\main.rs:3] i = 24
(this continues infinitely.)
Eerily, it seems that pwsh treats a pipe-to-finished-program as an infinite sink, not a broken pipe! This means that in pwsh, &"D:\.rust\target\release\playground.exe" | more
and inputting q
will hang until you CTRL-C the pipeline. In cmd, nushell-on-cmd, and Git Bash
For completeness sake, under Git Bash (a MINGW64), "D:\.rust\target\release\playground.exe" | sh -c 'exit'
consistently gives fatal runtime error: I/O error: operation failed to complete synchronously
. Additionally, under all tested shells (Git Bash, pwsh, cmd, nushell-on-cmd), doing the equivalent of > /dev/null
and CTRL-Cing the pipeline exited without a panic, as did the equivalent of | more > /dev/null
1. (As opposed to on a properly POSIX shell with SIGPIPE, where AIUI non-final entries in the pipeline get SIGPIPE and continue running if they SIG_IGN it, rather than receiving SIGINT? Leading to panics?)
Footnotes
-
New info! Most of the time
"D:\.rust\target\release\playground.exe" | less > /dev/null
in Git Bash hangs until CTRL-C exits without a panic. (q
is not accepted by this pipeline.) But sometimes when I've just run with| sh -c 'exit'
piping toless
(with or without> /dev/null
) will result infatal runtime error: I/O error: operation failed to complete synchronously
, and once I even just gotfatal runtime error:
with no more info. (I'm still running the same pc-windows-msvc build from MINGW.) ↩
Error on broken pipe but do not backtrace or ICE Windows will report a broken pipe as a normal error which in turn `println!` will panic on. Currently this causes rustc to produce a backtrace and ICE. However, this is not a bug with rustc so a backtrace is overly verbose and ultimately unhelpful to the user. Kind of fixes rust-lang#98700. Although this is admittedly a bit of a hack because at panic time all we have is a string to inspect. On zulip it was suggested that libstd might someday provide a way to indicate a soft panic but that day isn't today.
Code
After more testing, this only reproduces consistently under cmd, as other Windows shells have diverging behavior about what it means to close a pipe via program termination 🙃
rustc --help | cmd /c exit
does not panic, but this appears to be not because of rustc doing anything different, but becauserustc
finishes writing to stdout before it is closed.Meta
Note: never occurs under
#[cfg(unix)]
, as rustc setsSIGPIPE
toSIG_DFL
.rustc_driver::set_sigpipe_handler
rust/compiler/rustc_driver/src/lib.rs
Lines 433 to 440 in 7f08d04
rustc --version --verbose
:Error output
Backtrace
The text was updated successfully, but these errors were encountered: