mirror of
https://github.com/Hopiu/lychee.git
synced 2026-05-21 12:01:52 +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 std::sync::Arc;
|
||||||
|
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
|
@ -69,10 +70,10 @@ where
|
||||||
let verbose = cfg.verbose;
|
let verbose = cfg.verbose;
|
||||||
async move {
|
async move {
|
||||||
while let Some(response) = recv_resp.recv().await {
|
while let Some(response) = recv_resp.recv().await {
|
||||||
show_progress(&pb, &response, verbose);
|
show_progress(&pb, &response, verbose)?;
|
||||||
stats.add(response);
|
stats.add(response);
|
||||||
}
|
}
|
||||||
(pb, stats)
|
Ok((pb, stats))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -93,7 +94,8 @@ where
|
||||||
// the show_results_task to finish
|
// the show_results_task to finish
|
||||||
drop(send_req);
|
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
|
// Note that print statements may interfere with the progress bar, so this
|
||||||
// must go before printing the stats
|
// must go before printing the stats
|
||||||
|
|
@ -139,7 +141,11 @@ async fn handle(client: &Client, cache: Arc<Cache>, request: Request) -> Respons
|
||||||
response
|
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);
|
let out = color_response(&response.1);
|
||||||
if let Some(pb) = progress_bar {
|
if let Some(pb) = progress_bar {
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
|
|
@ -149,8 +155,9 @@ fn show_progress(progress_bar: &Option<ProgressBar>, response: &Response, verbos
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (response.status().is_success() || response.status().is_excluded()) && !verbose {
|
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
|
// required for apple silicon
|
||||||
use ring as _;
|
use ring as _;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
use openssl_sys as _; // required for vendored-openssl feature
|
use openssl_sys as _; // required for vendored-openssl feature
|
||||||
use ring as _;
|
use ring as _;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{self, BufRead, BufReader, ErrorKind, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
|
||||||
let modified = metadata.modified().ok()?;
|
let modified = metadata.modified().ok()?;
|
||||||
let elapsed = modified.elapsed().ok()?;
|
let elapsed = modified.elapsed().ok()?;
|
||||||
if elapsed > cfg.max_cache_age {
|
if elapsed > cfg.max_cache_age {
|
||||||
println!(
|
eprintln!(
|
||||||
"Cache is too old (age: {}, max age: {}). Discarding",
|
"Cache is too old (age: {}, max age: {}). Discarding",
|
||||||
humantime::format_duration(elapsed),
|
humantime::format_duration(elapsed),
|
||||||
humantime::format_duration(cfg.max_cache_age)
|
humantime::format_duration(cfg.max_cache_age)
|
||||||
|
|
@ -173,7 +173,7 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
|
||||||
match cache {
|
match cache {
|
||||||
Ok(cache) => Some(cache),
|
Ok(cache) => Some(cache),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while loading cache: {e}. Continuing without.");
|
eprintln!("Error while loading cache: {e}. Continuing without.");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +181,8 @@ fn load_cache(cfg: &Config) -> Option<Cache> {
|
||||||
|
|
||||||
/// Set up runtime and call lychee entrypoint
|
/// Set up runtime and call lychee entrypoint
|
||||||
fn run_main() -> Result<i32> {
|
fn run_main() -> Result<i32> {
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
let opts = load_config()?;
|
let opts = load_config()?;
|
||||||
let runtime = match opts.config.threads {
|
let runtime = match opts.config.threads {
|
||||||
Some(threads) => {
|
Some(threads) => {
|
||||||
|
|
@ -194,7 +196,24 @@ fn run_main() -> Result<i32> {
|
||||||
None => tokio::runtime::Runtime::new()?,
|
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
|
/// Run lychee on the given inputs
|
||||||
|
|
@ -244,10 +263,10 @@ fn write_stats(stats: ResponseStats, cfg: &Config) -> Result<()> {
|
||||||
} else {
|
} else {
|
||||||
if cfg.verbose && !is_empty {
|
if cfg.verbose && !is_empty {
|
||||||
// separate summary from the verbose list of links above
|
// 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
|
// we assume that the formatted stats don't have a final newline
|
||||||
println!("{formatted}");
|
writeln!(io::stdout(), "{formatted}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ impl Input {
|
||||||
let content: InputContent = Self::path_content(&path).await?;
|
let content: InputContent = Self::path_content(&path).await?;
|
||||||
yield content;
|
yield content;
|
||||||
}
|
}
|
||||||
Err(e) => println!("{e:?}"),
|
Err(e) => eprintln!("{e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue