as-tree
as-tree copied to clipboard
Thread panic - broken pipe when piping output to pager
To reproduce, any of these commands cause a thread panic when piping the output of as-tree to a pager (tested with less, more, most, bat):
ls /usr/lib | as-tree | less (same when replacing less with more, most, bat, etc)
fd | as-tree | bat
fd | as-tree | cat | less
With RUST_BACKTRACE=1, the trace output is:
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', library/std/src/io/stdio.rs:1021:9
stack backtrace:
0: rust_begin_unwind
at /build/rust/src/rustc-1.49.0-src/library/std/src/panicking.rs:495:5
1: std::panicking::begin_panic_fmt
at /build/rust/src/rustc-1.49.0-src/library/std/src/panicking.rs:437:5
2: std::io::stdio::print_to
at /build/rust/src/rustc-1.49.0-src/library/std/src/io/stdio.rs:1021:9
3: std::io::stdio::_print
at /build/rust/src/rustc-1.49.0-src/library/std/src/io/stdio.rs:1033:5
4: as_tree::PathTrie::_print
5: as_tree::PathTrie::_print
6: as_tree::PathTrie::_print
7: as_tree::main
Just tested a little further. I suspected this is was because as-tree was getting cut off while writing to stdout, so I tested a few short cases:
ls ~ | as-tree | less
No issue (assuming only a single page of output)
Using a longer output:
ls /usr/lib | as-tree | less
- Scroll all the way to the end of the buffer, no issue.
- Don't scroll to the end, press
q- Thread panic
I was just about to open an issue for precisely this. Would be great to have as-tree gracefully exit in this scenario.
Happy to look into this if you can provide a test case. Without a way to reproduce thereβs not much I can do.
On Wed, Feb 10, 2021 at 9:44 AM Charles Strahan [email protected] wrote:
I was just about to open an issue for precisely this. Would be great to have as-tree gracefully exit in this scenario.
β You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jez/as-tree/issues/15#issuecomment-776890894, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABKJUVE2DL7PC3GILZZZCJTS6LAYRANCNFSM4XJQIJSA .
@jez assuming your terminal height is less than 10,000 characters (quite probable :sweat_smile:), this should do the trick:
seq 10000 | as-tree | less -FirSwX
then press q to exit less.
@cstrahan awesome, thanks for the concise reproducer. I see
...
βββ 1048
βββ 1049
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', src/libstd/io/stdio.rs:805:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1] 71608 done seq 10000 |
71609 abort as-tree |
71610 done less -FirSwX
when i run it, which looks like the above.
If you want a reproducer that doesn't depend on a tty:
This uses a sleep so that the "a" char is piped into as-tree after the bash process exits:
$ (sleep 1; echo a) | as-tree | bash -c ''
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', src/libstd/io/stdio.rs:878:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
If you have process at the end of your pipeline that consumes all of as-tree's stdout then you don't run into this problem because that guarantees that the process outlives as-tree:
$ (sleep 1; echo a) | as-tree | bash -c 'cat /dev/stdin >/dev/null'
The problem with using a pager (right now) is that it won't necessary consume all of its stdin (e.g. as-tree's stdout): it might buffer just enough to show within the dimensions of the terminal, and then the user might exit the pager before the pager ever consumes all of stdin. If the pager doesn't consume all of its stdin, then as-tree will be blocked on writing to as-tree's stdout and then become unblocked with a write failure as the pipe is broken.
You can remove the sleep from my previous examples by using stdbuf to disable output buffering for as-tree's stdout; also, the bash -c '' can be replaced with a subshell that just immediately exits:
$ ( echo a ) | stdbuf -o0 as-tree | ( exit )
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', src/libstd/io/stdio.rs:878:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
$ ( echo a ) | stdbuf -o0 as-tree | ( cat /dev/stdin > /dev/null )
FWIW, looks like this is how fd handles this:
https://github.com/sharkdp/fd/blob/b928af7d9c4c174791cb23a58bbc4e724c2fbb9b/src/output.rs#L36-L39
if r.is_err() {
// Probably a broken pipe. Exit gracefully.
process::exit(ExitCode::GeneralError.into());
}
Similar to how exa handles it as well:
https://github.com/ogham/exa/blob/13b91cced4cab012413b25c9d3e30c63548639d0/src/main.rs#L75-L78
Err(e) if e.kind() == ErrorKind::BrokenPipe => {
warn!("Broken pipe error: {}", e);
exit(exits::SUCCESS);
}