fix(ecstore): map disk-representable StorageError variants in reverse conversion (#5980)

The StorageError -> DiskError conversion dropped seven variants with
exact DiskError counterparts (FaultyRemoteDisk, DiskAccessDenied,
DriveIsRoot, IsNotRegular, VolumeNotEmpty, VolumeAccessDenied,
FileAccessDenied) into the DiskError::other fallback, degrading them to
an opaque Io error. Two of them sit on the quorum ignore-lists in
disk/error_reduce.rs, so a degraded instance would stop matching the
ignore list and count toward the dominant error in reduce_errs.

Also mirror the StorageError-side io::Error downdrill in
From<io::Error> for DiskError: recover a StorageError boxed through
From<StorageError> for io::Error instead of wrapping it as Io.

Add a round-trip identity test over every DiskError variant
(DiskError -> StorageError -> DiskError) and a boxed-StorageError
recovery test.
This commit is contained in:
Zhengchao An
2026-08-12 16:43:04 +08:00
committed by GitHub
parent c7233d6624
commit 698ebdfb3f
2 changed files with 79 additions and 1 deletions
+29 -1
View File
@@ -331,7 +331,14 @@ impl From<std::io::Error> for DiskError {
}
match e.downcast::<DiskError>() {
Ok(disk_error) => disk_error,
Err(io_error) => DiskError::Io(io_error),
// Mirror `From<io::Error> for StorageError`: a StorageError boxed
// through `From<StorageError> for io::Error` must recover its typed
// classification instead of degrading to `DiskError::Io`, which
// quorum aggregation (`reduce_errs`) would count as a distinct error.
Err(io_error) => match io_error.downcast::<crate::error::StorageError>() {
Ok(storage_error) => storage_error.into(),
Err(io_error) => DiskError::Io(io_error),
},
}
}
}
@@ -953,6 +960,27 @@ mod tests {
assert_eq!(original_disk_error, recovered_disk_error);
}
#[test]
fn test_io_error_with_storage_error_inside() {
use crate::error::StorageError;
// An io::Error boxing a disk-representable StorageError (as produced by
// `From<StorageError> for io::Error`) must recover the typed DiskError
// variant instead of degrading to an opaque DiskError::Io.
let io_with_storage_error: std::io::Error = StorageError::FaultyRemoteDisk.into();
let recovered: DiskError = io_with_storage_error.into();
assert_eq!(recovered, DiskError::FaultyRemoteDisk);
let io_with_storage_error: std::io::Error = StorageError::FileAccessDenied.into();
let recovered: DiskError = io_with_storage_error.into();
assert_eq!(recovered, DiskError::FileAccessDenied);
// A StorageError with no DiskError analog stays an opaque Io error.
let io_with_bucket_error: std::io::Error = StorageError::BucketNotFound("bucket".to_string()).into();
let recovered: DiskError = io_with_bucket_error.into();
assert!(matches!(recovered, DiskError::Io(_)));
}
#[test]
fn test_io_error_different_kinds() {
use std::io::ErrorKind;