Handle broken pipe errors on stdout gracefully (#932)

Replace `println!` calls on the results paths with `writeln!` on
`io::stdout()` and propagate the `io::Error` to `main`, where
`ErrorKind::BrokenPipe` is turned into a quiet `exit(0)`. This matches
the conventional behavior of command-line tools when their output pipe
is closed early (e.g. `hyperfine ... | head -n 0`), and fixes panics in
all run-output paths: the per-benchmark header and results, the speed
comparison summary, and `-`-style exports to stdout. The warnings and
"Note" outputs to stderr are converted the same way.


---------

Co-authored-by: Mathjk <Mathjk@users.noreply.github.com>
Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
authored and GitHub committed 2026-10-02 11:18:56 +02:00
1 parent 4d073e08ae
commit c0b3834277
7 files changed
+118 -30

No files matched your search

+16 -2
View File
@@ -4,10 +4,12 @@
)]
use std::env;
use std::io::{self, Write};
use benchmark::scheduler::Scheduler;
use cli::get_cli_arguments;
use command::Commands;
use error::ConsoleOutputError;
use export::ExportManager;
use options::Options;
@@ -44,17 +46,29 @@ fn run() -> Result<()> {
let mut scheduler = Scheduler::new(&commands, &options, &export_manager);
scheduler.run_benchmarks()?;
scheduler.print_relative_speed_comparison();
scheduler.print_relative_speed_comparison()?;
scheduler.final_export()?;
Ok(())
}
fn caused_by_broken_console_pipe(error: &anyhow::Error) -> bool {
error
.downcast_ref::<ConsoleOutputError>()
.is_some_and(|e| e.0.kind() == io::ErrorKind::BrokenPipe)
}
fn main() {
match run() {
Ok(_) => {}
Err(e) => {
eprintln!("{} {:#}", "Error:".red(), e);
// Exit quietly when a reader stops consuming console output early.
if caused_by_broken_console_pipe(&e) {
std::process::exit(0);
}
// The write to stderr can itself fail if stderr is closed; the
// error message cannot be shown in that case anyway.
let _ = writeln!(io::stderr(), "{} {:#}", "Error:".red(), e);
std::process::exit(1);
}
}