mirror of
https://github.com/Hopiu/lychee.git
synced 2026-03-25 17:00:26 +00:00
Fix broken pipe error on failing writes to stdout (#535)
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: https://github.com/jez/as-tree/issues/15
This commit is contained in:
parent
595a713b4b
commit
4c51fce22f
3 changed files with 40 additions and 14 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use std::io::{self, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
|
|
@ -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))
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -173,7 +173,7 @@ 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
|
||||
}
|
||||
}
|
||||
|
|
@ -181,6 +181,8 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
|
|||
|
||||
/// 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) => {
|
||||
|
|
@ -194,7 +196,24 @@ fn run_main() -> Result<i32> {
|
|||
None => tokio::runtime::Runtime::new()?,
|
||||
};
|
||||
|
||||
runtime.block_on(run(&opts))
|
||||
match runtime.block_on(run(&opts)) {
|
||||
Err(e) if Some(ErrorKind::BrokenPipe) == underlying_io_error_kind(&e) => {
|
||||
exit(ExitCode::Success as i32);
|
||||
}
|
||||
res => res,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -244,10 +263,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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ impl Input {
|
|||
let content: InputContent = Self::path_content(&path).await?;
|
||||
yield content;
|
||||
}
|
||||
Err(e) => println!("{e:?}"),
|
||||
Err(e) => eprintln!("{e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue