Skip to content

Commit

Permalink
Fix broken pipe error on failing writes to stdout
Browse files Browse the repository at this point in the history
Make sure that broken pipes (e.g. when a reader of a
pipe prematurely exits during execution) get handled gracefully.
This change also moves some error messages to stderr by using
eprintln.

More info: jez/as-tree#15
  • Loading branch information
mre committed Mar 1, 2022
1 parent 0fc5fc9 commit 4e05254
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 14 deletions.
19 changes: 13 additions & 6 deletions lychee-bin/src/commands/check.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::io::{self, Write};
use std::sync::Arc;

use indicatif::ProgressBar;
Expand Down Expand Up @@ -69,10 +70,10 @@ where
let verbose = cfg.verbose;
async move {
while let Some(response) = recv_resp.recv().await {
show_progress(&pb, &response, verbose);
show_progress(&pb, &response, verbose)?;
stats.add(response);
}
(pb, stats)
Ok((pb, stats))
}
});

Expand All @@ -93,7 +94,8 @@ where
// the show_results_task to finish
drop(send_req);

let (pb, stats) = show_results_task.await?;
let result: Result<(_, _)> = show_results_task.await?;
let (pb, stats) = result?;

// Note that print statements may interfere with the progress bar, so this
// must go before printing the stats
Expand Down Expand Up @@ -139,7 +141,11 @@ async fn handle(client: &Client, cache: Arc<Cache>, request: Request) -> Respons
response
}

fn show_progress(progress_bar: &Option<ProgressBar>, response: &Response, verbose: bool) {
fn show_progress(
progress_bar: &Option<ProgressBar>,
response: &Response,
verbose: bool,
) -> Result<()> {
let out = color_response(&response.1);
if let Some(pb) = progress_bar {
pb.inc(1);
Expand All @@ -149,8 +155,9 @@ fn show_progress(progress_bar: &Option<ProgressBar>, response: &Response, verbos
}
} else {
if (response.status().is_success() || response.status().is_excluded()) && !verbose {
return;
return Ok(());
}
println!("{out}");
writeln!(io::stdout(), "{out}")?;
}
Ok(())
}
37 changes: 30 additions & 7 deletions lychee-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ use lychee_lib::Collector;
// required for apple silicon
use ring as _;

use anyhow::{Context, Result};
use anyhow::{Context, Error, Result};
use openssl_sys as _; // required for vendored-openssl feature
use ring as _;
use std::fs::{self, File};
use std::io::{BufRead, BufReader};
use std::io::{self, BufRead, BufReader, ErrorKind, Write};
use std::sync::Arc;
use structopt::StructOpt;

Expand Down Expand Up @@ -159,7 +159,7 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
let modified = metadata.modified().ok()?;
let elapsed = modified.elapsed().ok()?;
if elapsed > cfg.max_cache_age {
println!(
eprintln!(
"Cache is too old (age: {}, max age: {}). Discarding",
humantime::format_duration(elapsed),
humantime::format_duration(cfg.max_cache_age)
Expand All @@ -173,14 +173,16 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
match cache {
Ok(cache) => Some(cache),
Err(e) => {
println!("Error while loading cache: {e}. Continuing without.");
eprintln!("Error while loading cache: {e}. Continuing without.");
None
}
}
}

/// Set up runtime and call lychee entrypoint
fn run_main() -> Result<i32> {
use std::process::exit;

let opts = load_config()?;
let runtime = match opts.config.threads {
Some(threads) => {
Expand All @@ -194,7 +196,28 @@ fn run_main() -> Result<i32> {
None => tokio::runtime::Runtime::new()?,
};

runtime.block_on(run(&opts))
match runtime.block_on(run(&opts)) {
Ok(exit_code) => exit(exit_code),
Err(e) if Some(ErrorKind::BrokenPipe) == underlying_io_error_kind(&e) => {
exit(ExitCode::Success as i32);
}
Err(e) => {
eprintln!("{}", e);
exit(ExitCode::UnexpectedFailure as i32);
}
}
}

/// Check if the given error can be traced back to an `io::ErrorKind`
/// This is helpful for troubleshooting the root cause of an error.
/// Code is taken from the anyhow documentation.
fn underlying_io_error_kind(error: &Error) -> Option<io::ErrorKind> {
for cause in error.chain() {
if let Some(io_error) = cause.downcast_ref::<io::Error>() {
return Some(io_error.kind());
}
}
None
}

/// Run lychee on the given inputs
Expand Down Expand Up @@ -244,10 +267,10 @@ fn write_stats(stats: ResponseStats, cfg: &Config) -> Result<()> {
} else {
if cfg.verbose && !is_empty {
// separate summary from the verbose list of links above
println!();
writeln!(io::stdout(), "")?;
}
// we assume that the formatted stats don't have a final newline
println!("{formatted}");
writeln!(io::stdout(), "{formatted}")?;
}
Ok(())
}
2 changes: 1 addition & 1 deletion lychee-lib/src/types/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ impl Input {
let content: InputContent = Self::path_content(&path).await?;
yield content;
}
Err(e) => println!("{e:?}"),
Err(e) => eprintln!("{e:?}"),
}
}
}
Expand Down

0 comments on commit 4e05254

Please sign in to comment.