From c65d4071a3918a7126258e64cdcfe7a1e21b229b Mon Sep 17 00:00:00 2001 From: cxymds Date: Wed, 13 May 2026 21:03:15 +0800 Subject: [PATCH] fix(ecstore): map missing metadata to not found (#2944) Co-authored-by: houseme --- crates/ecstore/src/set_disk.rs | 30 +++++++++++++++++++++++++ crates/ecstore/src/set_disk/metadata.rs | 26 +++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/crates/ecstore/src/set_disk.rs b/crates/ecstore/src/set_disk.rs index b3466d016..4af2d4fdf 100644 --- a/crates/ecstore/src/set_disk.rs +++ b/crates/ecstore/src/set_disk.rs @@ -5559,6 +5559,36 @@ mod tests { assert_eq!(result, None); // No UUID meets quorum of 2 } + #[test] + fn test_object_quorum_from_meta_returns_not_found_when_all_metadata_is_missing() { + let errs = vec![ + Some(DiskError::FileNotFound), + Some(DiskError::VolumeNotFound), + Some(DiskError::DiskNotFound), + Some(DiskError::FileNotFound), + ]; + + let err = SetDisks::object_quorum_from_meta(&vec![FileInfo::default(); errs.len()], &errs, 2) + .expect_err("missing metadata should map to FileNotFound"); + + assert_eq!(err, DiskError::FileNotFound); + } + + #[test] + fn test_object_quorum_from_meta_preserves_read_quorum_for_mixed_failures() { + let errs = vec![ + Some(DiskError::FileNotFound), + Some(DiskError::VolumeNotFound), + Some(DiskError::FileCorrupt), + Some(DiskError::DiskNotFound), + ]; + + let err = SetDisks::object_quorum_from_meta(&vec![FileInfo::default(); errs.len()], &errs, 2) + .expect_err("mixed metadata failures should keep quorum semantics"); + + assert_eq!(err, DiskError::ErasureReadQuorum); + } + #[test] fn test_shuffle_parts_metadata() { // Test metadata shuffling diff --git a/crates/ecstore/src/set_disk/metadata.rs b/crates/ecstore/src/set_disk/metadata.rs index 1c59b5f16..13295a95b 100644 --- a/crates/ecstore/src/set_disk/metadata.rs +++ b/crates/ecstore/src/set_disk/metadata.rs @@ -15,6 +15,28 @@ use super::*; impl SetDisks { + pub(super) fn all_not_found_metadata(errs: &[Option]) -> bool { + !errs.is_empty() + && errs.iter().all(|err| match err { + Some(err) => { + matches!( + err, + DiskError::FileNotFound + | DiskError::FileVersionNotFound + | DiskError::VolumeNotFound + | DiskError::DiskNotFound + ) || OBJECT_OP_IGNORED_ERRS.contains(err) + } + None => false, + }) + && errs.iter().any(|err| { + matches!( + err, + Some(DiskError::FileNotFound | DiskError::FileVersionNotFound | DiskError::VolumeNotFound) + ) + }) + } + pub(super) fn reduce_common_data_dir(data_dirs: &Vec>, write_quorum: usize) -> Option { let mut data_dirs_count = HashMap::new(); @@ -237,6 +259,10 @@ impl SetDisks { errs: &[Option], default_parity_count: usize, ) -> disk::error::Result<(i32, i32)> { + if Self::all_not_found_metadata(errs) { + return Err(DiskError::FileNotFound); + } + let expected_rquorum = if default_parity_count == 0 { parts_metadata.len() } else {