From 899d0518d305c101a9d8248e6858157755af5ddf Mon Sep 17 00:00:00 2001 From: "R. Eric Collins" Date: Tue, 20 Oct 2026 02:55:03 -0500 Subject: [PATCH 1/2] Buffer --output-file writes OutputWriter wrapped a bare File, so each small write!() was its own write syscall (~1 per byte). Writing a 1.49M-contig x 399-sample metabat matrix took ~66 min on Lustre, as long as reading the BAMs. Wrap the file in a BufWriter, and flush it explicitly on drop so a failed final write is logged rather than silently truncating the output. Fixes #306 Co-Authored-By: Claude Opus 5.5 --- src/lib.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b50cc0e..95ac113 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,9 @@ impl FlagFilter { } pub struct OutputWriter { - pub output_file: Option>>, + // Buffered: output is written in many small pieces (one per field), and an + // unbuffered File turns each into a write syscall. + pub output_file: Option>>>, } impl OutputWriter { @@ -95,9 +97,9 @@ impl OutputWriter { info!("Writing output to file: {file_path}"); OutputWriter { output_file: Some(Arc::new(std::sync::Mutex::new( - std::fs::File::create(path).unwrap_or_else(|_| { - panic!("Failed to create output file: {}", file_path) - }), + std::io::BufWriter::new(std::fs::File::create(path).unwrap_or_else( + |_| panic!("Failed to create output file: {}", file_path), + )), ))), } } @@ -125,6 +127,20 @@ impl std::io::Write for OutputWriter { } } +impl Drop for OutputWriter { + // BufWriter flushes when dropped but ignores errors, so flush here and + // report a failed final write (e.g. a full disk) instead of truncating silently. + fn drop(&mut self) { + if let Some(f) = &self.output_file { + if let Ok(mut f) = f.lock() { + if let Err(e) = std::io::Write::flush(&mut *f) { + error!("Failed to flush output file: {e}"); + } + } + } + } +} + impl Clone for OutputWriter { fn clone(&self) -> OutputWriter { OutputWriter { From 896cd71db29473ab0996ad66839875e102b747f8 Mon Sep 17 00:00:00 2001 From: "R. Eric Collins" Date: Tue, 20 Oct 2026 04:57:05 -0500 Subject: [PATCH 2/2] Fail on a buffered output write error at finalisation With --output-file buffered, the last writes reach the disk only when the buffer is flushed, and the Drop impl just logged a failure there, so a full disk could leave a truncated file behind exit status 0. Flush in CoveragePrinter::finalise_printing, which every output path ends in, and exit 1 on error. Drop the logging-only Drop impl. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/coverage_printer.rs | 7 +++++++ src/lib.rs | 17 ++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/coverage_printer.rs b/src/coverage_printer.rs index 042f88b..0ed4b8a 100644 --- a/src/coverage_printer.rs +++ b/src/coverage_printer.rs @@ -118,6 +118,13 @@ impl CoveragePrinter { } } } + // Output to a file is buffered, so the last writes only reach the disk + // here; a failure (e.g. a full disk) must not leave a truncated file + // behind a successful exit. + if let Err(e) = print_stream.flush() { + error!("Failed to write output: {e}"); + process::exit(1); + } } pub fn print_headers( diff --git a/src/lib.rs b/src/lib.rs index 95ac113..ab83500 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,8 @@ impl FlagFilter { pub struct OutputWriter { // Buffered: output is written in many small pieces (one per field), and an - // unbuffered File turns each into a write syscall. + // unbuffered File turns each into a write syscall. Flushed, with errors + // checked, by CoveragePrinter::finalise_printing. pub output_file: Option>>>, } @@ -127,20 +128,6 @@ impl std::io::Write for OutputWriter { } } -impl Drop for OutputWriter { - // BufWriter flushes when dropped but ignores errors, so flush here and - // report a failed final write (e.g. a full disk) instead of truncating silently. - fn drop(&mut self) { - if let Some(f) = &self.output_file { - if let Ok(mut f) = f.lock() { - if let Err(e) = std::io::Write::flush(&mut *f) { - error!("Failed to flush output file: {e}"); - } - } - } - } -} - impl Clone for OutputWriter { fn clone(&self) -> OutputWriter { OutputWriter {