Compare commits

...

2 Commits

Author SHA1 Message Date
唐小鸭 c0745b7fb7 test(scanner): look up failed objects by the scanner's key form
The scanner records failed objects under keys built with path_join_buf,
which uses `/` separators on every platform. Three erasure probe tests
looked them up with a native `root.join(..)` path, so on Windows the
expected key never matched and the assertions failed. Build the expected
key the same way the scanner does; the assertions still require the exact
key to be present.
2026-11-10 20:23:01 +08:00
唐小鸭 9dff5b6aea fix(windows): release rebuild source pins and align test fixtures
Release the multipart source directory pin as soon as the upload head is
decoded during online index rebuild. On Windows the pin denies delete
sharing, so keeping it while indexing made a concurrent abort fail with a
sharing violation.

Align three test fixtures with Windows path and handle semantics:
- heal bucket pass targets use the endpoint string form, matching
  production disk endpoint identity;
- scanner erasure probe injection keys are normalized with path_join_buf
  so they match the scanner's lookup keys;
- the targets queue budget test changes mtime through a writable handle,
  which Windows requires.
2026-11-09 11:26:04 +08:00
4 changed files with 31 additions and 6 deletions
@@ -192,6 +192,10 @@ impl RebuildCursor {
Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
Err(error) => return Err(error),
};
// The head is decoded; release the source pin before indexing. On
// Windows the pin denies delete sharing, so holding it would make a
// concurrent abort of this upload fail with a sharing violation.
drop(source);
let source_root = relative
.file_name()
.and_then(|name| name.to_str())
+7 -1
View File
@@ -385,9 +385,15 @@ mod tests {
.is_err(),
"a stale worker cannot mutate a recreated bucket"
);
// Name targets as the store's endpoints render them (`C:/...` on Windows),
// not as raw OS paths.
let targets = directories
.iter()
.map(|dir| dir.path().to_string_lossy().into_owned())
.map(|dir| {
crate::heal::Endpoint::try_from(dir.path().to_str().expect("UTF-8 temp disk path"))
.expect("temp disk endpoint")
.to_string()
})
.collect::<Vec<_>>();
assert!(
storage
@@ -17,9 +17,18 @@ static ORDERED_DIRECTORIES: LazyLock<Mutex<HashMap<String, DirectoryOrder>>> = L
pub(in crate::scanner_folder) struct OrderedDirectory(String);
/// The failed-object key the scanner records for `relative` under `root`.
/// Production builds it with `path_join_buf`, which uses `/` separators on
/// every platform, so a native `root.join(..)` never matches on Windows.
fn failed_object_key(root: &std::path::Path, relative: &str) -> String {
rustfs_utils::path::path_join_buf(&[&root.join(relative).to_string_lossy()])
}
impl OrderedDirectory {
fn new(path: &std::path::Path) -> Self {
let path = path.to_string_lossy().into_owned();
// Key the probe by the cleaned form the scanner opens; on Windows a joined
// `C:\root\bucket/object` would otherwise never match `C:/root/bucket/object`.
let path = rustfs_utils::path::path_join_buf(&[&path.to_string_lossy()]);
ORDERED_DIRECTORIES
.lock()
.expect("ordered directory registry")
@@ -136,7 +145,7 @@ async fn erasure_candidate_finds_late_part_without_scanning_data_children() {
.new_cache
.info
.failed_objects
.contains_key(root.join("bucket/object/xl.meta").to_string_lossy().as_ref())
.contains_key(&failed_object_key(&root, "bucket/object/xl.meta"))
);
assert_eq!(budget.reason(), None, "payload directories must not consume namespace directory quota");
assert!(scanner.new_cache.info.pending_heals.iter().any(|pending| {
@@ -473,7 +482,7 @@ async fn erasure_candidate_cancelled_batch_keeps_partial_checkpoint_and_reclassi
.new_cache
.info
.failed_objects
.contains_key(root.join("bucket/xl.meta").to_string_lossy().as_ref())
.contains_key(&failed_object_key(&root, "bucket/xl.meta"))
);
assert_eq!(
charged_ops(&root),
@@ -532,7 +541,7 @@ async fn erasure_late_data_sibling_prevents_parent_coverage_certification() {
.new_cache
.info
.failed_objects
.contains_key(root.join("bucket/object/xl.meta").to_string_lossy().as_ref())
.contains_key(&failed_object_key(&root, "bucket/object/xl.meta"))
);
}
+7 -1
View File
@@ -1456,7 +1456,13 @@ mod tests {
store.put_failed_raw("failed-event", &[4; 16]).unwrap();
assert_eq!(store.stored_bytes.load(Ordering::SeqCst), 48);
let failed = store.failed_dir().join("failed-event");
File::open(&failed).unwrap().set_modified(UNIX_EPOCH).unwrap();
// Windows needs write-attribute access on the handle to change mtime.
std::fs::OpenOptions::new()
.write(true)
.open(&failed)
.unwrap()
.set_modified(UNIX_EPOCH)
.unwrap();
assert_eq!(store.prune_failed_store().unwrap(), 1);
assert_eq!(store.stored_bytes.load(Ordering::SeqCst), 32);
std::fs::create_dir(store.failed_dir().join("cannot-replace-directory")).unwrap();