format: bound and batch every chunk fetch over Storage
Only the full read split its chunk fetches into 64 MiB batches. The selection path, the indexed read and the parallel_read decoders fetched every chunk's stored bytes in one read_ranges call, each extent bounded only by the file length, so a crafted chunk index pointing many chunks at one large extent made File::open_storage hold chunks x extent bytes (3.3 GB from a 16.8 MB file) before the first decode error. - storage::for_each_extent_batch is now the one way raw-data reads fetch chunk bytes: batches of at most RAW_BATCH_BYTES (now pub), each decoded before the next is fetched. Used by the full, cached, indexed, selection and parallel_read paths; the sweep read uses read_extent per chunk. - ExtentReq carries each chunk's claimed extent (bounds-checked as before, same errors) and the prefix actually fetched: filters::stored_chunk_limit — the chunk size if unfiltered, else each applied filter's worst-case growth (n + n/4 + 4096 per codec; unbounded only for an application-registered codec). The in-memory path cuts the slice it decodes the same way, so both paths still agree. - tests/raw_fetch_bounds.rs: a crafted chunked_large.h5 (ten chunks all claiming 20 MiB at one padding blob) read through every path over a storage that records the largest single fetch; and 160 MiB of legitimate unfiltered chunks fetched batch by batch. Before: one 80 MiB fetch (selection) and one 160 MiB fetch; after: within the budget. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -349,9 +349,11 @@ impl File {
|
||||
/// selections, variable-length data, virtual datasets).
|
||||
///
|
||||
/// Every read goes through the storage's [`Storage::read_at`] and
|
||||
/// [`Storage::read_ranges`] (a chunked read fetches all the chunks it
|
||||
/// needs with one `read_ranges` call per 64 MiB), so nothing is read that
|
||||
/// the operation does not need. A storage that has the whole file in
|
||||
/// [`Storage::read_ranges`] (a chunked read fetches the chunks it needs
|
||||
/// with one `read_ranges` call per batch of at most 64 MiB, decoding
|
||||
/// each batch before the next, and never more of a chunk than its
|
||||
/// decoded size can need), so nothing is read that the operation does
|
||||
/// not need. A storage that has the whole file in
|
||||
/// memory ([`Storage::as_contiguous`]) is read as [`File::from_bytes`]
|
||||
/// reads its buffer. The storage holds the whole file: a user block is
|
||||
/// found and skipped, and bytes past the end of file the superblock
|
||||
|
||||
Reference in New Issue
Block a user