fix(uio): async disk-cache open panics on Populate::Partial for zero-length objects and empty ranges
Summary
Follow-up to #10625, same class one arm over: the PreferBackground/Blocking arm of the disk cache's open_async was fixed to skip fetching zero-length objects, but the Populate::Partial arm right below still does
let (block_range, fetch_range) =
block_aligned_fetch(byte_range, file_len).expect("range should not be empty");(lib/common/common/src/universal_io/simple_disk_cache/async_io.rs:73)
block_aligned_fetch returns None - and the async open therefore panics - in three cases the sync open handles gracefully (fs.rs:219-255):
- Zero-length object +
Partial(0..HEADER):file_len = 0makes the block-aligned range empty. This is the same premise as #10625 ("a single empty object anywhere under a segment prefix"), andPartialis how the cold/default placement prefetches headers: id tracker (disk_id_tracker/reader/lifecycle.rs:29), compact flags (read_only_compact_flags.rs:68), graph links, prefix index, etc.preopen/schedule_openroute through parkedopen_asyncfutures (cached_fs/mod.rs:286), so a zero-length flags or id-tracker object panics the segment open instead of failing cleanly. - Empty
Partialrange (e.g.or_partial(10..10)): sync treats this as a legal lazy open - there is an explicit arm for it (fs.rs:219) and a test (partial_populate_empty_range_is_lazy). Async panics. Partialrange entirely past EOF: same - sync testpartial_populate_range_past_eof_is_lazydocuments lazy open; async panics.
A panic in a segment-open task is also worse than the clean error the sync path produces: depending on the caller it can abort the whole open instead of degrading to "segment skipped".
Repro
Standalone harness with a path dependency on common, using a mock async-only remote (pattern copied from simple_disk_cache/tests.rs). Ran twice, identical output:
zero-length object, Partial(0..64):
sync open: Ok
async open: PANIC(range should not be empty)
non-empty object, empty Partial(0..0):
sync open: Ok
async open: PANIC(range should not be empty)
non-empty object, Partial past EOF:
sync open: Ok
async open: PANIC(range should not be empty)Proposed fix
Mirror the sync path: create the local mirror first and skip the fetch when block_aligned_fetch returns None, instead of panicking. PR with regression tests follows.
Source: qdrant/qdrant