From 16c2d1e1d05e815ef5b564cb7710b80853b75066 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 13 Dec 2023 14:23:14 -0500 Subject: [PATCH] walk: Flush stdout in batches The previous behaviour was designed to mimic the output buffering of typical UNIX tools: line-buffered if stdout is a TTY, and fully-buffered otherwise. More precicely, when printing to a terminal, fd would flush explicitly after printing any buffered results, then flush after every single result once streaming mode started. When not printing to a terminal, fd never explicitly flushed, so writes would only happen as the BufWriter filled up. The new behaviour actually unifies the TTY and non-TTY cases: we flush after printing the buffered results, then once we start streaming, we flush after each batch, but *only when the channel is empty*. This provides a good balance: if the channel is empty, the receiver thread might as well flush before it goes to sleep waiting for more results. If the channel is non-empty, we might as well process those results before deciding to flush. For TTYs, this should improve performance by consolidating write() calls without sacrificing interactivity. For non-TTYs, we'll be flushing more often, but only when the receiver would otherwise have nothing to do, thus improving interactivity without sacrificing performance. This is particularly handy when fd is piped into another command (such as head or grep): with the old behaviour, fd could wait for the whole traversal to finish before printing anything. With the new behaviour, fd will print those results soon after they are received. Fixes #1313. --- src/walk.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/walk.rs b/src/walk.rs index c6737ed..c81d2a4 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -214,7 +214,6 @@ impl<'a, W: Write> ReceiverBuffer<'a, W> { } ReceiverMode::Streaming => { self.print(&dir_entry)?; - self.flush()?; } } @@ -232,6 +231,11 @@ impl<'a, W: Write> ReceiverBuffer<'a, W> { } } } + + // If we don't have another batch ready, flush before waiting + if self.mode == ReceiverMode::Streaming && self.rx.is_empty() { + self.flush()?; + } } Err(RecvTimeoutError::Timeout) => { self.stream()?; @@ -285,7 +289,7 @@ impl<'a, W: Write> ReceiverBuffer<'a, W> { /// Flush stdout if necessary. fn flush(&mut self) -> Result<(), ExitCode> { - if self.config.interactive_terminal && self.stdout.flush().is_err() { + if self.stdout.flush().is_err() { // Probably a broken pipe. Exit gracefully. return Err(ExitCode::GeneralError); }