-
Notifications
You must be signed in to change notification settings - Fork 13.3k
rustc -C help
panics (ICEs) when piped to a closed stdout
#98700
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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: