mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 16:37:07 +00:00
refactor(ecstore): make store-to-disk error narrowing named and fallible (#6626)
refactor(ecstore): make store-to-disk error narrowing a named fallible operation Backlog#1845 step 4. The blanket impl From<StorageError> for DiskError let ? silently push store-only errors (locks, buckets, quotas) across the disk boundary into DiskError::other, where the rendered message fragments reduce_errs quorum buckets. Same story for the blanket From<StorageError> for rustfs_filemeta::Error and its other() catch-all. Both impls are replaced by named, fallible methods: StorageError::narrow_to_disk() and StorageError::narrow_to_filemeta(). Variants with an identity on the far side map across unchanged - including the two documented lossy collapses (SlowDown -> TooManyOpenFiles, StorageFull -> DiskFull) that the round-trip tests pin - and everything else returns Err(self) so the call site decides what crossing the boundary means. Removing the impls let the compiler enumerate every conversion site; the census that scoped this issue had found 5, the compiler found 33. Call sites keep their existing behavior: the io identity bridge and the generic sites fold Err into the io-backed other() exactly as the old catch-all did (identity still recoverable by downcast), listing paths use one shared to_filemeta_err helper, and the two sites that relied on the SlowDown collapse now construct DiskError::TooManyOpenFiles directly so the loss is visible where it happens. No behavior change intended anywhere; the io::Error bridge itself is untouched by design. Ref rustfs/backlog#1845
This commit is contained in:
@@ -678,7 +678,9 @@ mod tests {
|
|||||||
fn embedded_tonic_status_is_recovered_across_error_conversions() {
|
fn embedded_tonic_status_is_recovered_across_error_conversions() {
|
||||||
// DiskError and StorageError share one wrapper, so a status keeps its
|
// DiskError and StorageError share one wrapper, so a status keeps its
|
||||||
// typed classification whichever error it was converted into first.
|
// typed classification whichever error it was converted into first.
|
||||||
let from_storage: DiskErrorType = crate::error::Error::from(tonic::Status::unavailable("peer gone")).into();
|
let from_storage: DiskErrorType = crate::error::Error::from(tonic::Status::unavailable("peer gone"))
|
||||||
|
.narrow_to_disk()
|
||||||
|
.expect("status-derived Io errors narrow through the bridge");
|
||||||
let DiskError::Io(io_err) = &from_storage else {
|
let DiskError::Io(io_err) = &from_storage else {
|
||||||
panic!("status-derived disk error should stay an Io error");
|
panic!("status-derived disk error should stay an Io error");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3593,13 +3593,23 @@ mod tests {
|
|||||||
// reduce_errs groups Io errors by kind plus rendered message: peers failing the
|
// reduce_errs groups Io errors by kind plus rendered message: peers failing the
|
||||||
// same operation must stay a single dominant error instead of one bucket per peer.
|
// same operation must stay a single dominant error instead of one bucket per peer.
|
||||||
let per_peer_errs = (0..4)
|
let per_peer_errs = (0..4)
|
||||||
.map(|_| Some(DiskError::from(peer_failure_without_details("load_bucket_metadata", Some("shared")))))
|
.map(|_| {
|
||||||
|
Some(
|
||||||
|
peer_failure_without_details("load_bucket_metadata", Some("shared"))
|
||||||
|
.narrow_to_disk()
|
||||||
|
.unwrap_or_else(DiskError::other),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let (count, dominant) = reduce_errs(&per_peer_errs, &[]);
|
let (count, dominant) = reduce_errs(&per_peer_errs, &[]);
|
||||||
assert_eq!(count, 4, "one shared failure must not split into per-peer buckets");
|
assert_eq!(count, 4, "one shared failure must not split into per-peer buckets");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dominant,
|
dominant,
|
||||||
Some(DiskError::from(peer_failure_without_details("load_bucket_metadata", Some("shared"))))
|
Some(
|
||||||
|
peer_failure_without_details("load_bucket_metadata", Some("shared"))
|
||||||
|
.narrow_to_disk()
|
||||||
|
.unwrap_or_else(DiskError::other)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
|
|||||||
@@ -775,9 +775,10 @@ fn try_acquire_bucket_heal_movement_guard<'a>(
|
|||||||
};
|
};
|
||||||
// Do not queue a receiver behind a movement writer while its coordinator
|
// Do not queue a receiver behind a movement writer while its coordinator
|
||||||
// holds another node's read guard; failing fast breaks that cross-node cycle.
|
// holds another node's read guard; failing fast breaks that cross-node cycle.
|
||||||
gate.try_read()
|
// `StorageError::SlowDown` has no disk-layer identity of its own: it
|
||||||
.map(Some)
|
// collapses to `TooManyOpenFiles` at this boundary (see `narrow_to_disk`).
|
||||||
.map_err(|_| crate::error::StorageError::SlowDown.into())
|
// Constructed directly so the loss stays explicit at the site.
|
||||||
|
gate.try_read().map(Some).map_err(|_| Error::TooManyOpenFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn acquire_bucket_heal_write_guard<'a>(
|
async fn acquire_bucket_heal_write_guard<'a>(
|
||||||
@@ -787,7 +788,9 @@ async fn acquire_bucket_heal_write_guard<'a>(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let guard = gate.lock().await;
|
let guard = gate.lock().await;
|
||||||
guard.ensure_write_safe("bucket heal cannot run while pool metadata requires recovery")?;
|
guard
|
||||||
|
.ensure_write_safe("bucket heal cannot run while pool metadata requires recovery")
|
||||||
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(Error::other))?;
|
||||||
Ok(Some(guard))
|
Ok(Some(guard))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2366,7 +2369,11 @@ mod tests {
|
|||||||
|
|
||||||
let err = try_acquire_bucket_heal_movement_guard(Some(&gate), false)
|
let err = try_acquire_bucket_heal_movement_guard(Some(&gate), false)
|
||||||
.expect_err("receiver must not wait behind a queued movement writer");
|
.expect_err("receiver must not wait behind a queued movement writer");
|
||||||
assert_eq!(err, crate::error::StorageError::SlowDown.into());
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Error::TooManyOpenFiles,
|
||||||
|
"SlowDown collapses to TooManyOpenFiles at the disk boundary"
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
try_acquire_bucket_heal_movement_guard(Some(&gate), true)
|
try_acquire_bucket_heal_movement_guard(Some(&gate), true)
|
||||||
.expect("coordinator-owned movement guard should be reused")
|
.expect("coordinator-owned movement guard should be reused")
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ impl From<std::io::Error> for DiskError {
|
|||||||
// classification instead of degrading to `DiskError::Io`, which
|
// classification instead of degrading to `DiskError::Io`, which
|
||||||
// quorum aggregation (`reduce_errs`) would count as a distinct error.
|
// quorum aggregation (`reduce_errs`) would count as a distinct error.
|
||||||
Err(io_error) => match io_error.downcast::<crate::error::StorageError>() {
|
Err(io_error) => match io_error.downcast::<crate::error::StorageError>() {
|
||||||
Ok(storage_error) => storage_error.into(),
|
Ok(storage_error) => storage_error.narrow_to_disk().unwrap_or_else(DiskError::other),
|
||||||
Err(io_error) => DiskError::Io(io_error),
|
Err(io_error) => DiskError::Io(io_error),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1189,7 +1189,7 @@ mod tests {
|
|||||||
&storage,
|
&storage,
|
||||||
crate::error::StorageError::RemoteClientUnavailable(detail) if detail == "handshake timed out"
|
crate::error::StorageError::RemoteClientUnavailable(detail) if detail == "handshake timed out"
|
||||||
));
|
));
|
||||||
let narrowed: DiskError = storage.into();
|
let narrowed: DiskError = storage.narrow_to_disk().expect("typed variant must narrow");
|
||||||
assert_eq!(narrowed, original);
|
assert_eq!(narrowed, original);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
&narrowed,
|
&narrowed,
|
||||||
|
|||||||
@@ -5096,7 +5096,7 @@ impl LocalDisk {
|
|||||||
|
|
||||||
ensure_data_usage_layout(&io_root)
|
ensure_data_usage_layout(&io_root)
|
||||||
.await
|
.await
|
||||||
.map_err(DiskError::from)
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(DiskError::other))
|
||||||
.inspect_err(|err| {
|
.inspect_err(|err| {
|
||||||
log_startup_disk_error("ensure_data_usage_layout", &root, err);
|
log_startup_disk_error("ensure_data_usage_layout", &root, err);
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ fn heal_matched_disk_variants() -> Vec<DiskError> {
|
|||||||
fn disk_to_storage_to_disk_preserves_heal_matched_variants() {
|
fn disk_to_storage_to_disk_preserves_heal_matched_variants() {
|
||||||
for disk_err in heal_matched_disk_variants() {
|
for disk_err in heal_matched_disk_variants() {
|
||||||
let storage: StorageError = disk_err.clone().into();
|
let storage: StorageError = disk_err.clone().into();
|
||||||
let back: DiskError = storage.into();
|
let back: DiskError = storage.narrow_to_disk().expect("heal-matched variants must narrow");
|
||||||
assert_eq!(back, disk_err, "DiskError → StorageError → DiskError must be identity for {disk_err:?}");
|
assert_eq!(back, disk_err, "DiskError → StorageError → DiskError must be identity for {disk_err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,10 @@ fn storage_to_disk_to_storage_preserves_heal_matched_variants() {
|
|||||||
StorageError::ErasureWriteQuorum,
|
StorageError::ErasureWriteQuorum,
|
||||||
];
|
];
|
||||||
for storage_err in variants {
|
for storage_err in variants {
|
||||||
let disk: DiskError = storage_err.clone().into();
|
let disk: DiskError = storage_err
|
||||||
|
.clone()
|
||||||
|
.narrow_to_disk()
|
||||||
|
.expect("heal-matched variants must narrow");
|
||||||
let back: StorageError = disk.into();
|
let back: StorageError = disk.into();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
back, storage_err,
|
back, storage_err,
|
||||||
@@ -77,11 +80,11 @@ fn storage_to_disk_to_storage_preserves_heal_matched_variants() {
|
|||||||
/// A classifier on the far side of the disk boundary can no longer tell
|
/// A classifier on the far side of the disk boundary can no longer tell
|
||||||
/// backpressure ("please slow down") apart from fd exhaustion.
|
/// backpressure ("please slow down") apart from fd exhaustion.
|
||||||
///
|
///
|
||||||
/// Pinned as-is for backlog#1845; PR4's fallible `narrow_to_disk()` is the
|
/// The fallible `narrow_to_disk()` keeps this collapse as a documented arm;
|
||||||
/// planned place to surface this loss explicitly.
|
/// only variants with no disk-layer identity at all narrow to `Err`.
|
||||||
#[test]
|
#[test]
|
||||||
fn slowdown_collapses_to_too_many_open_files_across_disk_boundary() {
|
fn slowdown_collapses_to_too_many_open_files_across_disk_boundary() {
|
||||||
let disk: DiskError = StorageError::SlowDown.into();
|
let disk: DiskError = StorageError::SlowDown.narrow_to_disk().expect("SlowDown narrows, lossily");
|
||||||
assert_eq!(disk, DiskError::TooManyOpenFiles);
|
assert_eq!(disk, DiskError::TooManyOpenFiles);
|
||||||
|
|
||||||
let back: StorageError = disk.into();
|
let back: StorageError = disk.into();
|
||||||
@@ -96,7 +99,9 @@ fn slowdown_collapses_to_too_many_open_files_across_disk_boundary() {
|
|||||||
/// and comes back as `DiskFull`.
|
/// and comes back as `DiskFull`.
|
||||||
#[test]
|
#[test]
|
||||||
fn storage_full_collapses_to_disk_full_across_disk_boundary() {
|
fn storage_full_collapses_to_disk_full_across_disk_boundary() {
|
||||||
let disk: DiskError = StorageError::StorageFull.into();
|
let disk: DiskError = StorageError::StorageFull
|
||||||
|
.narrow_to_disk()
|
||||||
|
.expect("StorageFull narrows, lossily");
|
||||||
assert_eq!(disk, DiskError::DiskFull);
|
assert_eq!(disk, DiskError::DiskFull);
|
||||||
|
|
||||||
let back: StorageError = disk.into();
|
let back: StorageError = disk.into();
|
||||||
@@ -204,7 +209,10 @@ fn storage_to_filemeta_to_storage_preserves_matched_variants() {
|
|||||||
StorageError::Unexpected,
|
StorageError::Unexpected,
|
||||||
];
|
];
|
||||||
for storage_err in variants {
|
for storage_err in variants {
|
||||||
let filemeta: rustfs_filemeta::Error = storage_err.clone().into();
|
let filemeta: rustfs_filemeta::Error = storage_err
|
||||||
|
.clone()
|
||||||
|
.narrow_to_filemeta()
|
||||||
|
.expect("matched variants must narrow");
|
||||||
let back: StorageError = filemeta.into();
|
let back: StorageError = filemeta.into();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
back, storage_err,
|
back, storage_err,
|
||||||
@@ -219,7 +227,13 @@ fn storage_to_filemeta_to_storage_preserves_matched_variants() {
|
|||||||
/// recovers identity" property of the by-design bridge stays load-bearing.
|
/// recovers identity" property of the by-design bridge stays load-bearing.
|
||||||
#[test]
|
#[test]
|
||||||
fn storage_to_filemeta_other_recovers_identity_via_io_bridge() {
|
fn storage_to_filemeta_other_recovers_identity_via_io_bridge() {
|
||||||
let filemeta: rustfs_filemeta::Error = StorageError::SlowDown.into();
|
// A variant with no filemeta identity narrows to Err; callers that need a
|
||||||
|
// total conversion fold it into the io-backed other(), and the identity
|
||||||
|
// bridge recovers the boxed StorageError on the way back.
|
||||||
|
let refused = StorageError::SlowDown
|
||||||
|
.narrow_to_filemeta()
|
||||||
|
.expect_err("SlowDown has no filemeta identity");
|
||||||
|
let filemeta = rustfs_filemeta::Error::other(refused);
|
||||||
assert!(
|
assert!(
|
||||||
matches!(filemeta, rustfs_filemeta::Error::Io(_)),
|
matches!(filemeta, rustfs_filemeta::Error::Io(_)),
|
||||||
"unmatched variants fold into the io-backed other()"
|
"unmatched variants fold into the io-backed other()"
|
||||||
|
|||||||
@@ -327,9 +327,20 @@ impl From<DiskError> for StorageError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StorageError> for DiskError {
|
impl StorageError {
|
||||||
fn from(val: StorageError) -> Self {
|
/// Narrow a store-layer error to the disk-layer vocabulary.
|
||||||
match val {
|
///
|
||||||
|
/// This used to be a blanket `impl From<StorageError> for DiskError`, which
|
||||||
|
/// let `?` silently push store-only errors across the disk boundary into
|
||||||
|
/// `DiskError::other`, fragmenting `reduce_errs` quorum buckets
|
||||||
|
/// (backlog#1845). Narrowing is now a named, fallible operation: variants
|
||||||
|
/// with a disk-layer identity map across (including two documented lossy
|
||||||
|
/// collapses kept for compatibility — `SlowDown` → `TooManyOpenFiles` and
|
||||||
|
/// `StorageFull` → `DiskFull`, pinned by conversion_roundtrip_tests), and
|
||||||
|
/// everything else comes back as `Err(self)` so the caller decides what
|
||||||
|
/// crossing the boundary means for it.
|
||||||
|
pub fn narrow_to_disk(self) -> core::result::Result<DiskError, StorageError> {
|
||||||
|
Ok(match self {
|
||||||
StorageError::Io(io_error) => io_error.into(),
|
StorageError::Io(io_error) => io_error.into(),
|
||||||
StorageError::Unexpected => DiskError::Unexpected,
|
StorageError::Unexpected => DiskError::Unexpected,
|
||||||
StorageError::FileNotFound => DiskError::FileNotFound,
|
StorageError::FileNotFound => DiskError::FileNotFound,
|
||||||
@@ -375,8 +386,26 @@ impl From<StorageError> for DiskError {
|
|||||||
StorageError::VolumeAccessDenied => DiskError::VolumeAccessDenied,
|
StorageError::VolumeAccessDenied => DiskError::VolumeAccessDenied,
|
||||||
StorageError::FileAccessDenied => DiskError::FileAccessDenied,
|
StorageError::FileAccessDenied => DiskError::FileAccessDenied,
|
||||||
StorageError::RemoteClientUnavailable(detail) => DiskError::RemoteClientUnavailable(detail),
|
StorageError::RemoteClientUnavailable(detail) => DiskError::RemoteClientUnavailable(detail),
|
||||||
_ => DiskError::other(val),
|
val => return Err(val),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same contract as [`StorageError::narrow_to_disk`], for the
|
||||||
|
/// `rustfs_filemeta::Error` vocabulary (formerly a blanket `From` impl
|
||||||
|
/// with an `other()` catch-all). No production path currently narrows in
|
||||||
|
/// this direction; the named form keeps future callers deliberate.
|
||||||
|
pub fn narrow_to_filemeta(self) -> core::result::Result<rustfs_filemeta::Error, StorageError> {
|
||||||
|
Ok(match self {
|
||||||
|
StorageError::Unexpected => rustfs_filemeta::Error::Unexpected,
|
||||||
|
StorageError::FileNotFound => rustfs_filemeta::Error::FileNotFound,
|
||||||
|
StorageError::FileVersionNotFound => rustfs_filemeta::Error::FileVersionNotFound,
|
||||||
|
StorageError::FileCorrupt => rustfs_filemeta::Error::FileCorrupt,
|
||||||
|
StorageError::DoneForNow => rustfs_filemeta::Error::DoneForNow,
|
||||||
|
StorageError::MethodNotAllowed => rustfs_filemeta::Error::MethodNotAllowed,
|
||||||
|
StorageError::VolumeNotFound => rustfs_filemeta::Error::VolumeNotFound,
|
||||||
|
StorageError::Io(io_error) => io_error.into(),
|
||||||
|
val => return Err(val),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,22 +462,6 @@ impl From<rustfs_filemeta::Error> for StorageError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StorageError> for rustfs_filemeta::Error {
|
|
||||||
fn from(val: StorageError) -> Self {
|
|
||||||
match val {
|
|
||||||
StorageError::Unexpected => rustfs_filemeta::Error::Unexpected,
|
|
||||||
StorageError::FileNotFound => rustfs_filemeta::Error::FileNotFound,
|
|
||||||
StorageError::FileVersionNotFound => rustfs_filemeta::Error::FileVersionNotFound,
|
|
||||||
StorageError::FileCorrupt => rustfs_filemeta::Error::FileCorrupt,
|
|
||||||
StorageError::DoneForNow => rustfs_filemeta::Error::DoneForNow,
|
|
||||||
StorageError::MethodNotAllowed => rustfs_filemeta::Error::MethodNotAllowed,
|
|
||||||
StorageError::VolumeNotFound => rustfs_filemeta::Error::VolumeNotFound,
|
|
||||||
StorageError::Io(io_error) => io_error.into(),
|
|
||||||
_ => rustfs_filemeta::Error::other(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for StorageError {
|
impl PartialEq for StorageError {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
@@ -1438,7 +1451,9 @@ mod tests {
|
|||||||
|
|
||||||
for original in all_variants {
|
for original in all_variants {
|
||||||
let storage_error: StorageError = original.clone().into();
|
let storage_error: StorageError = original.clone().into();
|
||||||
let round_tripped: DiskError = storage_error.into();
|
let round_tripped: DiskError = storage_error
|
||||||
|
.narrow_to_disk()
|
||||||
|
.expect("every disk-representable StorageError must narrow back");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::mem::discriminant(&original),
|
std::mem::discriminant(&original),
|
||||||
@@ -1452,7 +1467,7 @@ mod tests {
|
|||||||
// message must both survive the round trip.
|
// message must both survive the round trip.
|
||||||
let io_original = DiskError::Io(IoError::new(ErrorKind::PermissionDenied, "denied"));
|
let io_original = DiskError::Io(IoError::new(ErrorKind::PermissionDenied, "denied"));
|
||||||
let storage_error: StorageError = io_original.clone().into();
|
let storage_error: StorageError = io_original.clone().into();
|
||||||
let io_round_tripped: DiskError = storage_error.into();
|
let io_round_tripped: DiskError = storage_error.narrow_to_disk().expect("Io narrows through the bridge");
|
||||||
assert_eq!(io_original, io_round_tripped);
|
assert_eq!(io_original, io_round_tripped);
|
||||||
match io_round_tripped {
|
match io_round_tripped {
|
||||||
DiskError::Io(inner) => {
|
DiskError::Io(inner) => {
|
||||||
@@ -1700,8 +1715,13 @@ mod tests {
|
|||||||
assert_eq!(converted_storage_error, expected_storage_error);
|
assert_eq!(converted_storage_error, expected_storage_error);
|
||||||
|
|
||||||
// Test reverse conversion
|
// Test reverse conversion
|
||||||
let converted_back: rustfs_filemeta::Error = converted_storage_error.into();
|
let converted_back: rustfs_filemeta::Error = converted_storage_error
|
||||||
assert_eq!(converted_back, expected_storage_error.into());
|
.narrow_to_filemeta()
|
||||||
|
.expect("matched variants must narrow to filemeta");
|
||||||
|
let expected_back: rustfs_filemeta::Error = expected_storage_error
|
||||||
|
.narrow_to_filemeta()
|
||||||
|
.expect("matched variants must narrow to filemeta");
|
||||||
|
assert_eq!(converted_back, expected_back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -474,13 +474,15 @@ impl SetDisks {
|
|||||||
// Bound, not `_`: this guard must live to the end of the scope. A bare
|
// Bound, not `_`: this guard must live to the end of the scope. A bare
|
||||||
// `_` would drop it here and release the namespace write lock.
|
// `_` would drop it here and release the namespace write lock.
|
||||||
let _write_lock_guard = if !opts.no_lock {
|
let _write_lock_guard = if !opts.no_lock {
|
||||||
let ns_lock = self.new_ns_lock(bucket, object).await?;
|
let ns_lock = self
|
||||||
Some(
|
.new_ns_lock(bucket, object)
|
||||||
ns_lock
|
|
||||||
.get_write_lock(get_lock_acquire_timeout())
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| self.map_namespace_lock_error(bucket, object, "write", e))?,
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(DiskError::other))?;
|
||||||
)
|
Some(ns_lock.get_write_lock(get_lock_acquire_timeout()).await.map_err(|e| {
|
||||||
|
self.map_namespace_lock_error(bucket, object, "write", e)
|
||||||
|
.narrow_to_disk()
|
||||||
|
.unwrap_or_else(DiskError::other)
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -2200,13 +2202,15 @@ impl crate::storage_api_contracts::heal::HealOperations for SetDisks {
|
|||||||
opts: &HealOpts,
|
opts: &HealOpts,
|
||||||
) -> Result<(HealResultItem, Option<Error>)> {
|
) -> Result<(HealResultItem, Option<Error>)> {
|
||||||
let _write_lock_guard = if !opts.no_lock {
|
let _write_lock_guard = if !opts.no_lock {
|
||||||
let ns_lock = self.new_ns_lock(bucket, object).await?;
|
let ns_lock = self
|
||||||
Some(
|
.new_ns_lock(bucket, object)
|
||||||
ns_lock
|
|
||||||
.get_write_lock(get_lock_acquire_timeout())
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| self.map_namespace_lock_error(bucket, object, "write", e))?,
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(DiskError::other))?;
|
||||||
)
|
Some(ns_lock.get_write_lock(get_lock_acquire_timeout()).await.map_err(|e| {
|
||||||
|
self.map_namespace_lock_error(bucket, object, "write", e)
|
||||||
|
.narrow_to_disk()
|
||||||
|
.unwrap_or_else(DiskError::other)
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -2306,13 +2310,15 @@ impl crate::storage_api_contracts::heal::HealOperations for SetDisks {
|
|||||||
async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()> {
|
async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()> {
|
||||||
let started_at = std::time::Instant::now();
|
let started_at = std::time::Instant::now();
|
||||||
let _write_lock_guard = if !opts.no_lock {
|
let _write_lock_guard = if !opts.no_lock {
|
||||||
let ns_lock = self.new_ns_lock(bucket, object).await?;
|
let ns_lock = self
|
||||||
Some(
|
.new_ns_lock(bucket, object)
|
||||||
ns_lock
|
|
||||||
.get_write_lock(get_lock_acquire_timeout())
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| self.map_namespace_lock_error(bucket, object, "write", e))?,
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(DiskError::other))?;
|
||||||
)
|
Some(ns_lock.get_write_lock(get_lock_acquire_timeout()).await.map_err(|e| {
|
||||||
|
self.map_namespace_lock_error(bucket, object, "write", e)
|
||||||
|
.narrow_to_disk()
|
||||||
|
.unwrap_or_else(DiskError::other)
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2205,7 +2205,7 @@ impl SetDisks {
|
|||||||
}
|
}
|
||||||
let current = read_object_transaction_epoch_fence(self, bucket, object)
|
let current = read_object_transaction_epoch_fence(self, bucket, object)
|
||||||
.await
|
.await
|
||||||
.map_err(DiskError::from)?;
|
.map_err(|e| e.narrow_to_disk().unwrap_or_else(DiskError::other))?;
|
||||||
let disks = self.get_disks_internal().await;
|
let disks = self.get_disks_internal().await;
|
||||||
let mut removed = 0usize;
|
let mut removed = 0usize;
|
||||||
|
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ mod tests {
|
|||||||
let _movement_guard = self
|
let _movement_guard = self
|
||||||
.movement_gate
|
.movement_gate
|
||||||
.try_read()
|
.try_read()
|
||||||
.map_err(|_| crate::error::StorageError::SlowDown)?;
|
.map_err(|_| crate::disk::error::DiskError::TooManyOpenFiles)?;
|
||||||
Ok(HealResultItem::default())
|
Ok(HealResultItem::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
/// Narrow a store error into the filemeta vocabulary for `MetaCacheEntriesSortedResult.err`.
|
||||||
|
/// Faithful port of the retired blanket `From<StorageError> for rustfs_filemeta::Error`:
|
||||||
|
/// unmatched variants fold into the io-backed `other()`, whose boxed identity the io bridge
|
||||||
|
/// recovers on the way back (see conversion_roundtrip_tests).
|
||||||
|
fn to_filemeta_err(err: Error) -> rustfs_filemeta::Error {
|
||||||
|
err.narrow_to_filemeta().unwrap_or_else(rustfs_filemeta::Error::other)
|
||||||
|
}
|
||||||
|
|
||||||
use crate::bucket::metadata_sys::{get_versioning_config, has_authoritative_never_versioned_state};
|
use crate::bucket::metadata_sys::{get_versioning_config, has_authoritative_never_versioned_state};
|
||||||
use crate::bucket::utils::check_list_objs_args;
|
use crate::bucket::utils::check_list_objs_args;
|
||||||
use crate::bucket::versioning::VersioningApi;
|
use crate::bucket::versioning::VersioningApi;
|
||||||
@@ -3162,7 +3170,7 @@ impl ECStore {
|
|||||||
.list_path(&page_opts)
|
.list_path(&page_opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let reached_end = match list_result.err.take() {
|
let reached_end = match list_result.err.take() {
|
||||||
@@ -3525,7 +3533,7 @@ impl ECStore {
|
|||||||
.list_path(&provider_opts)
|
.list_path(&provider_opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3788,7 +3796,7 @@ impl ECStore {
|
|||||||
.list_path(&opts)
|
.list_path(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -3928,7 +3936,7 @@ impl ECStore {
|
|||||||
.list_path(&opts)
|
.list_path(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -4109,7 +4117,7 @@ impl ECStore {
|
|||||||
match res{
|
match res{
|
||||||
Ok(err) => {
|
Ok(err) => {
|
||||||
log_list_path_worker_error("store", "worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("store", "worker_error", &log_context, err.as_ref());
|
||||||
MetaCacheEntriesSortedResult{ entries: None, err: Some(err.as_ref().clone().into()) }
|
MetaCacheEntriesSortedResult{ entries: None, err: Some(to_filemeta_err(err.as_ref().clone())) }
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log_list_path_worker_error("store", "error_channel_closed", &log_context, &err);
|
log_list_path_worker_error("store", "error_channel_closed", &log_context, &err);
|
||||||
@@ -4129,7 +4137,7 @@ impl ECStore {
|
|||||||
|
|
||||||
if let Ok(err) = err_rx.try_recv() {
|
if let Ok(err) = err_rx.try_recv() {
|
||||||
log_list_path_worker_error("store", "trailing_worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("store", "trailing_worker_error", &log_context, err.as_ref());
|
||||||
result.err = Some(err.as_ref().clone().into());
|
result.err = Some(to_filemeta_err(err.as_ref().clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.err.is_some() {
|
if result.err.is_some() {
|
||||||
@@ -4150,7 +4158,7 @@ impl ECStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truncated {
|
if !truncated {
|
||||||
result.err = Some(Error::Unexpected.into());
|
result.err = Some(rustfs_filemeta::Error::Unexpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4350,7 +4358,7 @@ impl ECStore {
|
|||||||
let reader_disks = disks.len();
|
let reader_disks = disks.len();
|
||||||
|
|
||||||
let path = base_dir_from_prefix(prefix);
|
let path = base_dir_from_prefix(prefix);
|
||||||
ensure_non_empty_listing_disks(bucket, &path, &disks)?;
|
ensure_non_empty_listing_disks(bucket, &path, &disks).map_err(to_filemeta_err)?;
|
||||||
|
|
||||||
let mut filter_prefix = {
|
let mut filter_prefix = {
|
||||||
prefix
|
prefix
|
||||||
@@ -4816,7 +4824,7 @@ async fn gather_results(
|
|||||||
o: MetaCacheEntries(entries),
|
o: MetaCacheEntries(entries),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
err: Some(Error::Unexpected.into()),
|
err: Some(rustfs_filemeta::Error::Unexpected),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -5133,7 +5141,7 @@ impl Sets {
|
|||||||
.list_path(&opts)
|
.list_path(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -5222,7 +5230,7 @@ impl Sets {
|
|||||||
.list_path(&opts)
|
.list_path(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -5383,7 +5391,7 @@ impl Sets {
|
|||||||
match res {
|
match res {
|
||||||
Ok(err) => {
|
Ok(err) => {
|
||||||
log_list_path_worker_error("sets", "worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("sets", "worker_error", &log_context, err.as_ref());
|
||||||
MetaCacheEntriesSortedResult { entries: None, err: Some(err.as_ref().clone().into()) }
|
MetaCacheEntriesSortedResult { entries: None, err: Some(to_filemeta_err(err.as_ref().clone())) }
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log_list_path_worker_error("sets", "error_channel_closed", &log_context, &err);
|
log_list_path_worker_error("sets", "error_channel_closed", &log_context, &err);
|
||||||
@@ -5398,7 +5406,7 @@ impl Sets {
|
|||||||
|
|
||||||
if let Ok(err) = err_rx.try_recv() {
|
if let Ok(err) = err_rx.try_recv() {
|
||||||
log_list_path_worker_error("sets", "trailing_worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("sets", "trailing_worker_error", &log_context, err.as_ref());
|
||||||
result.err = Some(err.as_ref().clone().into());
|
result.err = Some(to_filemeta_err(err.as_ref().clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.err.is_some() {
|
if result.err.is_some() {
|
||||||
@@ -5419,7 +5427,7 @@ impl Sets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truncated {
|
if !truncated {
|
||||||
result.err = Some(Error::Unexpected.into());
|
result.err = Some(rustfs_filemeta::Error::Unexpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5584,7 +5592,7 @@ impl Sets {
|
|||||||
let reader_disks = disks.len();
|
let reader_disks = disks.len();
|
||||||
|
|
||||||
let path = base_dir_from_prefix(prefix);
|
let path = base_dir_from_prefix(prefix);
|
||||||
ensure_non_empty_listing_disks(bucket, &path, &disks)?;
|
ensure_non_empty_listing_disks(bucket, &path, &disks).map_err(to_filemeta_err)?;
|
||||||
|
|
||||||
let mut filter_prefix = prefix
|
let mut filter_prefix = prefix
|
||||||
.trim_start_matches(&path)
|
.trim_start_matches(&path)
|
||||||
@@ -5911,7 +5919,7 @@ impl SetDisks {
|
|||||||
.list_path_result(&opts)
|
.list_path_result(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -6040,7 +6048,7 @@ impl SetDisks {
|
|||||||
.list_path_result(&opts)
|
.list_path_result(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -6128,7 +6136,7 @@ impl SetDisks {
|
|||||||
.list_path_result(&opts)
|
.list_path_result(&opts)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||||
err: Some(err.into()),
|
err: Some(to_filemeta_err(err)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
let next_cache_id = list_result.entries.as_ref().and_then(|entries| entries.list_id.clone());
|
||||||
@@ -6436,7 +6444,7 @@ impl SetDisks {
|
|||||||
match res {
|
match res {
|
||||||
Ok(err) => {
|
Ok(err) => {
|
||||||
log_list_path_worker_error("set_disks", "worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("set_disks", "worker_error", &log_context, err.as_ref());
|
||||||
MetaCacheEntriesSortedResult { entries: None, err: Some(err.as_ref().clone().into()) }
|
MetaCacheEntriesSortedResult { entries: None, err: Some(to_filemeta_err(err.as_ref().clone())) }
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log_list_path_worker_error("set_disks", "error_channel_closed", &log_context, &err);
|
log_list_path_worker_error("set_disks", "error_channel_closed", &log_context, &err);
|
||||||
@@ -6451,7 +6459,7 @@ impl SetDisks {
|
|||||||
|
|
||||||
if let Ok(err) = err_rx.try_recv() {
|
if let Ok(err) = err_rx.try_recv() {
|
||||||
log_list_path_worker_error("set_disks", "trailing_worker_error", &log_context, err.as_ref());
|
log_list_path_worker_error("set_disks", "trailing_worker_error", &log_context, err.as_ref());
|
||||||
result.err = Some(err.as_ref().clone().into());
|
result.err = Some(to_filemeta_err(err.as_ref().clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.err.is_some() {
|
if result.err.is_some() {
|
||||||
@@ -6472,7 +6480,7 @@ impl SetDisks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truncated {
|
if !truncated {
|
||||||
result.err = Some(Error::Unexpected.into());
|
result.err = Some(rustfs_filemeta::Error::Unexpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user