close
Skip to content

perf: use BufWriter<StdoutLock> for all verbose/listing output#163

Open
kaladron wants to merge 1 commit intouutils:mainfrom
kaladron:fast-t
Open

perf: use BufWriter<StdoutLock> for all verbose/listing output#163
kaladron wants to merge 1 commit intouutils:mainfrom
kaladron:fast-t

Conversation

@kaladron
Copy link
Copy Markdown
Collaborator

@kaladron kaladron commented Apr 8, 2026

Replace println! in list, create, and extract operations with writeln! on a BufWriter. This acquires stdout's mutex once per operation instead of once per write call, and batches writes to reduce write(2) syscalls.

Also propagates write errors (e.g. broken pipe) gracefully instead of panicking.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 5.55556% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.30%. Comparing base (2c7a5e6) to head (b469a73).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/uu/tar/src/operations/create.rs 0.00% 7 Missing ⚠️
src/uu/tar/src/operations/list.rs 0.00% 6 Missing ⚠️
src/uu/tar/src/operations/extract.rs 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #163       +/-   ##
===========================================
+ Coverage   60.83%   76.30%   +15.46%     
===========================================
  Files           7        9        +2     
  Lines         789      941      +152     
  Branches       24       24               
===========================================
+ Hits          480      718      +238     
+ Misses        309      223       -86     
Flag Coverage Δ
macos_latest 76.30% <5.55%> (+15.46%) ⬆️
ubuntu_latest 76.30% <5.55%> (+15.46%) ⬆️
windows_latest 0.00% <0.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Replace println! in list, create, and extract operations with
writeln! on a BufWriter<StdoutLock>. This acquires stdout's mutex
once per operation instead of once per write call, and batches
writes to reduce write(2) syscalls.

Also propagates write errors (e.g. broken pipe) gracefully instead
of panicking.
@kaladron kaladron marked this pull request as ready for review April 8, 2026 20:18
@kaladron
Copy link
Copy Markdown
Collaborator Author

kaladron commented Apr 8, 2026

@sylvestre I'm looking at your locking comment (I didn't know that the Rust standard library still did locking even on single-threaded programs, thanks!) and I'm wondering if your experience with Coreutils gives suggestion for how far to take this. For instance, in this CL I'm wrapping all the major operations since create/extract/list could all generate a significant amount of stdio work. Is there a good threshold? I'd appreciate guidance!

@kaladron
Copy link
Copy Markdown
Collaborator Author


// Create Builder instance
let mut builder = Builder::new(file);
let mut out = BufWriter::new(io::stdout().lock());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io::Stdout is internally a LineWriter; lock() hands you a StdoutLock that writes through that line writer. Wrapping it in a BufWriter adds an 8 KB buffer above the line writer, so on a terminal the user no longer sees output line-by-line. For tar -tvf big.tar interactively, output is delayed until the buffer fills or the function returns — a noticeable regression for slow archives where users currently get streaming feedback.

The cleaner fix that keeps the lock-once perf win without breaking interactive output is to drop the BufWriter and use io::stdout().lock() directly (the line writer already coalesces within a line), or use LineWriter::new(io::stdout().lock()) explicitly. You'd still get the mutex-once benefit; you'd lose the cross-line batching for piped output, but that's much smaller than the lock contention savings.

})?;
}

out.flush().map_err(TarError::Io)?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, return TarError::Io, which UError::code reports as 2, with the io error printed to stderr - is also non-conventional. GNU tar on tar tf foo.tar | head -1 exits via SIGPIPE (status 141) silently

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Can you please file that as an issue? I think it'll get lost in here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Create Archive instance
let mut archive = Archive::new(file);
let mut out = BufWriter::new(io::stdout().lock());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use this inside of verbose blocks, can we avoid creating it in non-verbose mode?

@oech3
Copy link
Copy Markdown
Contributor

oech3 commented Apr 14, 2026

Replace println!

It also avoids panic with >/dev/full.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants