fix(ecstore): optimize ObjectInfo clone and fix critical TODOs (#3149)

* fix(ecstore): optimize ObjectInfo clone and fix critical TODOs

## ObjectInfo hot-path clone optimization (issue #653 item 2)
- Wrap user_defined (HashMap), user_tags (String), parts (Vec) in Arc
- Clone cost reduced from ~20 heap allocations to O(1) ref count bump
- Updated 11 downstream access sites with explicit deref where needed

## Critical TODO/FIXME fixes (issue #653 item 7)
- rpc/peer_s3_client.rs: descriptive error message for empty peer response
- set_disk/write.rs: concurrent rollback deletes via tokio::spawn + join_all
- store_list_objects.rs: resolved FIXME with explanation + 4 regression tests
- store/bucket.rs: namespace write locks for make_bucket and delete_bucket

Skipped TODOs (too risky without broader context):
- multipart.rs:30 nslock — causes lock timeout in existing tests
- bucket.rs:88 cached list_bucket — needs cache invalidation strategy
- bucket.rs:149 replication delete — needs replication subsystem integration

1134 tests pass (1 flaky under parallel execution, passes individually).

* fix: address PR #3149 review comments

- Preserve NamespaceLockQuorumUnavailable variant in bucket create/delete
- Fix cancellation test to actually test cancellation (keep senders open)
- Update ObjectInfo consumers in app/ for Arc-backed fields
- Update bucket_usecase.rs user_tags to Arc<String>

* fix: update ObjectInfo Arc consumers

* fix: address ecstore merge review comments

* fix: cancel blocked merge output sends

* fix(ecstore): satisfy ObjectInfo clone clippy
This commit is contained in:
安正超
2026-06-01 08:44:58 +08:00
committed by GitHub
parent bd571a575f
commit 97dd825468
19 changed files with 479 additions and 162 deletions
+45 -2
View File
@@ -14,6 +14,7 @@
use super::*;
use crate::bucket::utils::is_meta_bucketname;
use crate::set_disk::get_lock_acquire_timeout;
fn should_override_created_from_metadata(created: OffsetDateTime) -> bool {
created != OffsetDateTime::UNIX_EPOCH
@@ -28,7 +29,28 @@ impl ECStore {
return Err(StorageError::BucketNameInvalid(err.to_string()));
}
// TODO: nslock
let _ns_guard = if !opts.no_lock {
let ns_lock = self.new_ns_lock(bucket, bucket).await?;
Some(
ns_lock
.get_write_lock(get_lock_acquire_timeout())
.await
.map_err(|e| match e {
rustfs_lock::error::LockError::QuorumNotReached { required, achieved } => {
StorageError::NamespaceLockQuorumUnavailable {
mode: "write",
bucket: bucket.to_string(),
object: bucket.to_string(),
required,
achieved,
}
}
other => StorageError::other(format!("make_bucket: failed to acquire write lock on {bucket}: {other}")),
})?,
)
} else {
None
};
if let Err(err) = self.peer_sys.make_bucket(bucket, opts).await {
let err = to_object_err(err.into(), vec![bucket]);
@@ -111,7 +133,28 @@ impl ECStore {
return Err(StorageError::BucketNameInvalid(err.to_string()));
}
// TODO: nslock
let _ns_guard = if !opts.no_lock {
let ns_lock = self.new_ns_lock(bucket, bucket).await?;
Some(
ns_lock
.get_write_lock(get_lock_acquire_timeout())
.await
.map_err(|e| match e {
rustfs_lock::error::LockError::QuorumNotReached { required, achieved } => {
StorageError::NamespaceLockQuorumUnavailable {
mode: "write",
bucket: bucket.to_string(),
object: bucket.to_string(),
required,
achieved,
}
}
other => StorageError::other(format!("delete_bucket: failed to acquire write lock on {bucket}: {other}")),
})?,
)
} else {
None
};
// Check bucket exists before deletion (per S3 API spec)
// If bucket doesn't exist, return NoSuchBucket error
+2 -2
View File
@@ -423,7 +423,7 @@ impl ECStore {
};
let put_opts = ObjectOptions {
user_defined: src_info.user_defined.clone(),
user_defined: (*src_info.user_defined).clone(),
versioned: dst_opts.versioned,
version_id: dst_opts.version_id.clone(),
no_lock: dst_opts.no_lock,
@@ -823,7 +823,7 @@ impl ECStore {
}
let (oi, _) = self.get_latest_accessible_object_info_with_idx(bucket, &object, opts).await?;
Ok(oi.user_tags)
Ok((*oi.user_tags).clone())
}
#[instrument(level = "debug", skip(self))]