diff --git a/crates/ecstore/src/set_disk/core/io_primitives.rs b/crates/ecstore/src/set_disk/core/io_primitives.rs index 9c76defd6..ff4a27816 100644 --- a/crates/ecstore/src/set_disk/core/io_primitives.rs +++ b/crates/ecstore/src/set_disk/core/io_primitives.rs @@ -1275,6 +1275,10 @@ pub(in crate::set_disk) fn fill_deferred_bitrot_readers( continue; } + if files[idx].data.is_none() && disks[idx].is_none() { + continue; + } + let inline_data = files[idx].data.clone(); let disk = disks[idx].clone(); let data_dir = files[idx].data_dir.unwrap_or_default(); diff --git a/crates/ecstore/src/set_disk/read.rs b/crates/ecstore/src/set_disk/read.rs index c8b3ec1d7..4a3015ecd 100644 --- a/crates/ecstore/src/set_disk/read.rs +++ b/crates/ecstore/src/set_disk/read.rs @@ -4095,6 +4095,27 @@ mod tests { assert_eq!(&out[..n], [b"aaaa", b"bbbb", b"cccc", b"dddd"][fallback_index]); } + #[tokio::test] + async fn bitrot_reader_setup_skips_deferred_slots_without_a_source() { + let setup = setup_inline_bitrot_readers_with_env( + vec![Some(b"aaaa"), Some(b"bbbb"), None, Some(b"dddd")], + 2, + 2, + BitrotReaderSetupMode::ReadQuorum, + true, + ) + .await; + + assert!(setup.has_setup_quorum(2, 2, BitrotReaderSetupMode::ReadQuorum)); + assert_eq!(setup.available_shards(), 2); + assert_eq!(setup.deferred_shards(), 1); + assert!(setup.readers[2].is_none(), "a slot without inline data or a disk has no usable source"); + assert!(matches!(setup.errors[2], Some(DiskError::DiskNotFound))); + assert!(setup.deferred_stripe_handles[2].is_none()); + assert!(setup.readers[3].is_some(), "an inline shard remains available as a deferred fallback"); + assert!(setup.deferred_stripe_handles[3].is_some()); + } + #[tokio::test] async fn bitrot_reader_setup_preference_uses_data_blocks_first_without_env() { let setup = setup_inline_bitrot_readers_with_preference(