Repository navigation
feat(sort): add --async-reader for prefetch I/O on input BAM - #261
Conversation
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughA hidden 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/lib/commands/common.rs (1)
122-124: Prefer constructing viaDefaultto avoid drift.Line 123 hardcodes
async_reader: false; using struct update fromDefaultkeeps constructor behavior aligned if defaults evolve.♻️ Suggested refactor
pub fn new(input: impl Into<PathBuf>, output: impl Into<PathBuf>) -> Self { - Self { input: input.into(), output: output.into(), async_reader: false } + Self { input: input.into(), output: output.into(), ..Self::default() } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/commands/common.rs` around lines 122 - 124, The constructor new currently hardcodes async_reader: false which can drift from the type's Default; change the initializer in pub fn new(input: impl Into<PathBuf>, output: impl Into<PathBuf>) -> Self to build from the type's Default using struct update syntax (e.g. set input: input.into(), output: output.into(), ..Default::default()) so async_reader and any future defaults stay synchronized with Default::default() for this type.src/lib/os_hints.rs (1)
42-65: Keep the raw-fd helpers crate-private.These APIs expose a lifetime-sensitive raw descriptor contract, but the docs describe them as
PrefetchReaderinternals. Making thempub(crate)keeps that footgun out of the crate’s public surface.Proposed visibility change
-pub fn advise_willneed_raw(fd: i32, offset: i64, len: i64) { +pub(crate) fn advise_willneed_raw(fd: i32, offset: i64, len: i64) { @@ -pub fn hint_fd(file: &File) -> Option<i32> { +pub(crate) fn hint_fd(file: &File) -> Option<i32> { @@ -pub fn hint_fd(_file: &File) -> Option<i32> { +pub(crate) fn hint_fd(_file: &File) -> Option<i32> {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/os_hints.rs` around lines 42 - 65, Change the visibility of the raw-fd helper APIs so they are crate-private: make advise_willneed_raw and both hint_fd definitions pub(crate) instead of pub; update the declaration for advise_willneed_raw and the two cfg-gated hint_fd functions (the #[cfg(target_os = "linux")] hint_fd and the #[cfg(not(target_os = "linux"))] hint_fd) to use pub(crate) to keep the raw descriptor contract internal to the crate.tests/integration/test_async_reader.rs (1)
96-127: Compare normalized headers too.This helper only checks records, so it would miss regressions in
@HD/@SQ/@RG/sort metadata. Stripping just the@PGcommand-line delta and asserting the rest of the header matches would make these tests cover the full output contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/integration/test_async_reader.rs` around lines 96 - 127, Extend assert_bam_records_equal to also read and compare the BAM headers: call the existing header-reading helper (or obtain header via the same reader used in read_bam_records) for both path1 and path2, remove/skip only the `@PG` header records from each header, normalize ordering if necessary (e.g., sort `@SQ/`@RG entries or compare canonical representations) and assert the remaining header lines/fields (`@HD`, `@SQ`, `@RG`, sort, etc.) are equal with a descriptive message; keep the existing record comparisons intact and fail with a clear message if headers differ.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/bam_io.rs`:
- Around line 1225-1230: The async prefetch path can hang on stdin because
PrefetchReader::drop() waits on join() while the stdin producer may block in
read(); change the condition that creates PrefetchReader so that when
opts.async_reader is true you also check the input is not "-" (stdin) before
constructing PrefetchReader, and otherwise fall back to returning the chained
reader; update the branch around PrefetchReader::new(chained) and the
Ok((Box::new(prefetch), header)) return to gate --async-reader off for "-" (or
implement an explicit cancellation mechanism on PrefetchReader before enabling
it).
In `@src/lib/commands/extract.rs`:
- Around line 510-514: The docstring for the async_reader flag is inaccurate:
update the comment above the async_reader field (the attribute with #[arg(long =
"async-reader", default_value_t = false, hide = true)] and the public field
async_reader) to remove the claim that it "has no effect on gzip/plain inputs"
and instead state that the flag wraps FASTQ inputs (including plain, gzip, and
BGZF) in a userspace async prefetch reader—reference open_fastq_reader() as the
implementation that now applies the wrapper to all FASTQ input types; keep the
notes about dedicating one OS thread per input and that the flag is
experimental/hidden.
In `@src/lib/unified_pipeline/fastq.rs`:
- Around line 3699-3723: The code currently enables PrefetchReader whenever
config.async_reader is true, which can violate the BGZF-only contract if
inputs_are_bgzf is false (or decompressed_readers == None is desynced); update
the logic in the mapping that constructs StreamReader::Bgzf so that
PrefetchReader is used only when both config.async_reader and inputs_are_bgzf
are true (i.e., change the branch to if config.async_reader && inputs_are_bgzf {
... } else { ... }), or alternatively add an early assertion like
assert!(!config.async_reader || inputs_are_bgzf, "async_reader requires BGZF
inputs") to enforce the invariants before creating the readers; reference the
config.async_reader flag, inputs_are_bgzf (or decompressed_readers presence) and
the StreamReader::Bgzf / PrefetchReader construction to locate where to change
the code.
---
Nitpick comments:
In `@src/lib/commands/common.rs`:
- Around line 122-124: The constructor new currently hardcodes async_reader:
false which can drift from the type's Default; change the initializer in pub fn
new(input: impl Into<PathBuf>, output: impl Into<PathBuf>) -> Self to build from
the type's Default using struct update syntax (e.g. set input: input.into(),
output: output.into(), ..Default::default()) so async_reader and any future
defaults stay synchronized with Default::default() for this type.
In `@src/lib/os_hints.rs`:
- Around line 42-65: Change the visibility of the raw-fd helper APIs so they are
crate-private: make advise_willneed_raw and both hint_fd definitions pub(crate)
instead of pub; update the declaration for advise_willneed_raw and the two
cfg-gated hint_fd functions (the #[cfg(target_os = "linux")] hint_fd and the
#[cfg(not(target_os = "linux"))] hint_fd) to use pub(crate) to keep the raw
descriptor contract internal to the crate.
In `@tests/integration/test_async_reader.rs`:
- Around line 96-127: Extend assert_bam_records_equal to also read and compare
the BAM headers: call the existing header-reading helper (or obtain header via
the same reader used in read_bam_records) for both path1 and path2, remove/skip
only the `@PG` header records from each header, normalize ordering if necessary
(e.g., sort `@SQ/`@RG entries or compare canonical representations) and assert the
remaining header lines/fields (`@HD`, `@SQ`, `@RG`, sort, etc.) are equal with a
descriptive message; keep the existing record comparisons intact and fail with a
clear message if headers differ.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 34e28527-e6b5-4a6f-811f-38bbaa6f99f2
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
Cargo.tomldocs/src/guide/performance-tuning.mdsrc/lib/bam_io.rssrc/lib/commands/clip.rssrc/lib/commands/codec.rssrc/lib/commands/common.rssrc/lib/commands/correct.rssrc/lib/commands/dedup.rssrc/lib/commands/downsample.rssrc/lib/commands/duplex.rssrc/lib/commands/extract.rssrc/lib/commands/filter.rssrc/lib/commands/group.rssrc/lib/commands/simplex.rssrc/lib/commands/sort.rssrc/lib/mod.rssrc/lib/os_hints.rssrc/lib/prefetch_reader.rssrc/lib/sort/raw.rssrc/lib/unified_pipeline/fastq.rstests/integration/main.rstests/integration/test_async_reader.rs
226eb0e to
7aaf60e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #261 +/- ##
==========================================
+ Coverage 89.87% 90.03% +0.16%
==========================================
Files 120 122 +2
Lines 61416 61771 +355
==========================================
+ Hits 55197 55615 +418
+ Misses 6219 6156 -63 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7aaf60e to
ed54f90
Compare
ed54f90 to
c98a3d7
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/bam_io.rs (1)
828-833: Update function docs to reflect async thread behavior.The docs above still say no extra threads are spawned, but
async_reader=truenow introduces a prefetch thread. Please align the comment with current behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/bam_io.rs` around lines 828 - 833, Update the function documentation for create_raw_bam_reader_pool_integrated to state that when async_reader is true a background prefetch thread is spawned (using the pooled read-ahead input stream) and that this changes threading/ownership considerations; mention that async_reader=false keeps the previous single-thread/no-extra-thread behavior, and reference the PooledInputStream/read_ahead mechanism and the async_reader parameter so callers know the concurrency impact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/bam_io.rs`:
- Around line 828-833: Update the function documentation for
create_raw_bam_reader_pool_integrated to state that when async_reader is true a
background prefetch thread is spawned (using the pooled read-ahead input stream)
and that this changes threading/ownership considerations; mention that
async_reader=false keeps the previous single-thread/no-extra-thread behavior,
and reference the PooledInputStream/read_ahead mechanism and the async_reader
parameter so callers know the concurrency impact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 47e7959e-210d-440c-b02f-59a63abad1b9
📒 Files selected for processing (6)
docs/src/guide/performance-tuning.mdsrc/lib/bam_io.rssrc/lib/commands/extract.rssrc/lib/commands/sort.rssrc/lib/sort/raw.rstests/integration/test_async_reader.rs
✅ Files skipped from review due to trivial changes (1)
- src/lib/commands/extract.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib/commands/sort.rs
- docs/src/guide/performance-tuning.md
- src/lib/sort/raw.rs
Wire the PrefetchReader (from nh/async-prefetch-reader) into the sort command's pool-integrated input path. When --async-reader is enabled, a dedicated I/O thread reads raw bytes into a bounded channel ahead of the pool's BGZF decompression workers, hiding disk latency during Phase 1. - Add hidden --async-reader flag to Sort CLI struct - Add async_reader field + builder to RawExternalSorter - Update create_raw_bam_reader_pool_integrated to conditionally wrap the file reader with PrefetchReader - Add 4 integration tests (coordinate 1T/4T, queryname 4T, template-coordinate 4T) comparing output with/without the flag - Update performance-tuning docs to mention sort support
c98a3d7 to
f1366dc
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/bam_io.rs (1)
880-890: Minor inconsistency:advise_sequentialonly in async branch.Other similar functions (
create_bam_reader_with_optsat line 713,create_bam_reader_for_pipeline_with_optsat line 1246) calladvise_sequentialunconditionally before the async check. Here it's inside theif async_readerblock.The kernel read-ahead hint benefits sequential reads regardless of PrefetchReader. Consider moving it before the conditional for consistency.
♻️ Proposed fix
+ crate::os_hints::advise_sequential(&file); + let reader: Box<dyn io::Read + Send> = if async_reader { - crate::os_hints::advise_sequential(&file); log::info!( "async sort reader enabled: spawning fgumi-prefetch thread for {}", path_ref.display()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/bam_io.rs` around lines 880 - 890, Move the call to crate::os_hints::advise_sequential(&file) out of the async branch so the kernel read-ahead hint is applied for both sync and async readers; specifically, in the reader-construction block that returns (header, reader) (the same area that constructs Box<dyn io::Read + Send>), invoke advise_sequential(&file) once before the if async_reader { ... } else { ... } so both the PrefetchReader::from_file(file) path and the io::BufReader::with_capacity(...) path benefit from the hint (mirror the approach used in create_bam_reader_with_opts and create_bam_reader_for_pipeline_with_opts).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/bam_io.rs`:
- Around line 880-890: Move the call to
crate::os_hints::advise_sequential(&file) out of the async branch so the kernel
read-ahead hint is applied for both sync and async readers; specifically, in the
reader-construction block that returns (header, reader) (the same area that
constructs Box<dyn io::Read + Send>), invoke advise_sequential(&file) once
before the if async_reader { ... } else { ... } so both the
PrefetchReader::from_file(file) path and the io::BufReader::with_capacity(...)
path benefit from the hint (mirror the approach used in
create_bam_reader_with_opts and create_bam_reader_for_pipeline_with_opts).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2d93f08f-9ece-415b-8dd2-a9585eef491b
📒 Files selected for processing (6)
docs/src/guide/performance-tuning.mdsrc/lib/bam_io.rssrc/lib/commands/extract.rssrc/lib/commands/sort.rssrc/lib/sort/raw.rstests/integration/test_async_reader.rs
✅ Files skipped from review due to trivial changes (1)
- src/lib/commands/extract.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- docs/src/guide/performance-tuning.md
- src/lib/commands/sort.rs
- src/lib/sort/raw.rs
- tests/integration/test_async_reader.rs
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
… remaining 48-commit work (#296) The final PR in the stacked series closes the full delta against the 48-commit backup. In addition to the original scope (vendored bam_codec deletion + simulate migration), this amends to include: - Missing new files: sort/keys.rs, 4 raw-bam bench files - compare/bams.rs RawRecord migration - codec_caller.rs cascade simplification - group/filter/correct/extract/clip/bam_io.rs simplifications - overlapping.rs raw-only API - Test suite updates for test_compare_bams, test_group_command, test_e2e_regression - Doc touch-ups in best-practices/performance-tuning Verified: tree matches backup except for 6 main-only files (prefetch_reader, os_hints, test_async_reader, template_coordinate, and two benches) that came from main PRs #258/#261/#265/#270/#282 merged after our branch point. Part 8 of 8 in the series closing #272. Stacks on #295.
Summary
PrefetchReader(from perf(io): async prefetch reader with POSIX_FADV_WILLNEED for BAM/FASTQ inputs #258) into the sort command's pool-integrated input path--async-readeris enabled, a dedicated I/O thread reads raw bytes into a bounded channel ahead of the pool's BGZF decompression workers, hiding disk latency during Phase 1--async-readerflag to Sort CLI,async_readerfield + builder toRawExternalSorter, and conditionalPrefetchReaderwrapping increate_raw_bam_reader_pool_integratedBenchmark (c7g.4xlarge, 6.5 GB BAM, template-coordinate, 8 threads, cold cache, gp3 @ 500 MB/s)
Async reader also reduces variance (baseline 52.9–55.9s vs async 51.0–52.9s).
Stacked on
nh/async-prefetch-reader)Test plan
cargo ci-test(2426 passed)cargo ci-fmtcleancargo ci-lintclean--ignoredintegration tests pass (require samtools)