refactor: NamespaceLock (nslock), AHM→Heal Crate, and Lock/Clippy Fixes (#1664)

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: weisd <2057561+weisd@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
weisd
2026-01-30 13:13:41 +08:00
committed by GitHub
parent 1c085590ca
commit dce117840c
80 changed files with 3787 additions and 16746 deletions
+8 -127
View File
@@ -51,51 +51,22 @@ impl DisabledLockManager {
}
/// Always succeeds - returns a no-op guard
pub async fn acquire_read_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(bucket, object, owner);
pub async fn acquire_read_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>>) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(key, owner);
self.acquire_lock(request).await
}
/// Always succeeds - returns a no-op guard
pub async fn acquire_read_lock_versioned(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
version: impl Into<Arc<str>>,
key: ObjectKey,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(bucket, object, owner).with_version(version);
let request = ObjectLockRequest::new_write(key, owner);
self.acquire_lock(request).await
}
/// Always succeeds - returns a no-op guard
pub async fn acquire_write_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_write(bucket, object, owner);
self.acquire_lock(request).await
}
/// Always succeeds - returns a no-op guard
pub async fn acquire_write_lock_versioned(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
version: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_write(bucket, object, owner).with_version(version);
self.acquire_lock(request).await
}
/// Always succeeds - all locks acquired
pub async fn acquire_locks_batch(&self, batch_request: BatchLockRequest) -> BatchLockResult {
let successful_locks: Vec<ObjectKey> = batch_request.requests.iter().map(|req| req.key.clone()).collect();
@@ -161,42 +132,12 @@ impl LockManager for DisabledLockManager {
self.acquire_lock(request).await
}
async fn acquire_read_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock(bucket, object, owner).await
async fn acquire_read_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock(key, owner).await
}
async fn acquire_read_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock_versioned(bucket, object, version, owner).await
}
async fn acquire_write_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock(bucket, object, owner).await
}
async fn acquire_write_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock_versioned(bucket, object, version, owner).await
async fn acquire_write_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock(key, owner).await
}
async fn acquire_locks_batch(&self, batch_request: BatchLockRequest) -> BatchLockResult {
@@ -235,63 +176,3 @@ impl LockManager for DisabledLockManager {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_disabled_manager_basic_operations() {
let manager = DisabledLockManager::new();
// All operations should succeed immediately
let read_guard = manager
.acquire_read_lock("bucket", "object", "owner1")
.await
.expect("Disabled manager should always succeed");
let write_guard = manager
.acquire_write_lock("bucket", "object", "owner2")
.await
.expect("Disabled manager should always succeed");
// Guards should indicate they are disabled
assert!(read_guard.is_disabled());
assert!(write_guard.is_disabled());
}
#[tokio::test]
async fn test_disabled_manager_batch_operations() {
let manager = DisabledLockManager::new();
let batch = BatchLockRequest::new("owner")
.add_read_lock("bucket", "obj1")
.add_write_lock("bucket", "obj2")
.with_all_or_nothing(true);
let result = manager.acquire_locks_batch(batch).await;
assert!(result.all_acquired);
assert_eq!(result.successful_locks.len(), 2);
assert!(result.failed_locks.is_empty());
}
#[tokio::test]
async fn test_disabled_manager_metrics() {
let manager = DisabledLockManager::new();
// Metrics should indicate empty/disabled state
let metrics = manager.get_metrics();
assert!(metrics.is_empty());
assert_eq!(manager.total_lock_count(), 0);
assert!(manager.get_pool_stats().is_empty());
}
#[tokio::test]
async fn test_disabled_manager_cleanup() {
let manager = DisabledLockManager::new();
// Cleanup should be no-op
assert_eq!(manager.cleanup_expired().await, 0);
assert_eq!(manager.cleanup_expired_traditional().await, 0);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,255 +0,0 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Example integration of FastObjectLockManager in set_disk.rs
// This shows how to replace the current slow lock system
use crate::fast_lock::{BatchLockRequest, FastObjectLockManager, ObjectLockRequest};
use std::sync::Arc;
use std::time::Duration;
/// Example integration into SetDisks structure
pub struct SetDisksWithFastLock {
/// Replace the old namespace_lock with fast lock manager
pub fast_lock_manager: Arc<FastObjectLockManager>,
pub locker_owner: String,
// ... other fields remain the same
}
impl SetDisksWithFastLock {
/// Example: Replace get_object_reader with fast locking
pub async fn get_object_reader_fast(
&self,
bucket: &str,
object: &str,
version: Option<&str>,
// ... other parameters
) -> Result<(), Box<dyn std::error::Error>> {
// Fast path: Try to acquire read lock immediately
let _read_guard = if let Some(v) = version {
// Version-specific lock
self.fast_lock_manager
.acquire_read_lock_versioned(bucket, object, v, self.locker_owner.as_str())
.await
.map_err(|_| "Lock acquisition failed")?
} else {
// Latest version lock
self.fast_lock_manager
.acquire_read_lock(bucket, object, self.locker_owner.as_str())
.await
.map_err(|_| "Lock acquisition failed")?
};
// Critical section: Read object
// The lock is automatically released when _read_guard goes out of scope
// ... actual read operation logic
Ok(())
}
/// Example: Replace put_object with fast locking
pub async fn put_object_fast(
&self,
bucket: &str,
object: &str,
version: Option<&str>,
// ... other parameters
) -> Result<(), Box<dyn std::error::Error>> {
// Acquire exclusive write lock with timeout
let request = ObjectLockRequest::new_write(bucket, object, self.locker_owner.as_str())
.with_acquire_timeout(Duration::from_secs(5))
.with_lock_timeout(Duration::from_secs(30));
let request = if let Some(v) = version {
request.with_version(v)
} else {
request
};
let _write_guard = self
.fast_lock_manager
.acquire_lock(request)
.await
.map_err(|_| "Lock acquisition failed")?;
// Critical section: Write object
// ... actual write operation logic
Ok(())
// Lock automatically released when _write_guard drops
}
/// Example: Replace delete_objects with batch fast locking
pub async fn delete_objects_fast(
&self,
bucket: &str,
objects: Vec<(&str, Option<&str>)>, // (object_name, version)
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
// Create batch request for atomic locking
let mut batch = BatchLockRequest::new(self.locker_owner.as_str()).with_all_or_nothing(true); // Either lock all or fail
// Add all objects to batch (sorted internally to prevent deadlocks)
for (object, version) in &objects {
let mut request = ObjectLockRequest::new_write(bucket, *object, self.locker_owner.as_str());
if let Some(v) = version {
request = request.with_version(*v);
}
batch.requests.push(request);
}
// Acquire all locks atomically
let batch_result = self.fast_lock_manager.acquire_locks_batch(batch).await;
if !batch_result.all_acquired {
return Err("Failed to acquire all locks for batch delete".into());
}
// Critical section: Delete all objects
let mut deleted = Vec::new();
for (object, _version) in objects {
// ... actual delete operation logic
deleted.push(object.to_string());
}
// All locks automatically released when guards go out of scope
Ok(deleted)
}
/// Example: Health check integration
pub fn get_lock_health(&self) -> crate::fast_lock::metrics::AggregatedMetrics {
self.fast_lock_manager.get_metrics()
}
/// Example: Cleanup integration
pub async fn cleanup_expired_locks(&self) -> usize {
self.fast_lock_manager.cleanup_expired().await
}
}
/// Performance comparison demonstration
pub mod performance_comparison {
use super::*;
use std::time::Instant;
pub async fn benchmark_fast_vs_old() {
let fast_manager = Arc::new(FastObjectLockManager::new());
let owner = "benchmark_owner";
// Benchmark fast lock acquisition
let start = Instant::now();
let mut guards = Vec::new();
for i in 0..1000 {
let guard = fast_manager
.acquire_write_lock("bucket", format!("object_{i}"), owner)
.await
.expect("Failed to acquire fast lock");
guards.push(guard);
}
let fast_duration = start.elapsed();
println!("Fast lock: 1000 acquisitions in {fast_duration:?}");
// Release all
drop(guards);
// Compare with metrics
let metrics = fast_manager.get_metrics();
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
println!("Average wait time: {:?}", metrics.shard_metrics.avg_wait_time());
println!("Total operations/sec: {:.2}", metrics.ops_per_second());
}
}
/// Migration guide from old to new system
pub mod migration_guide {
/*
Step-by-step migration from old lock system:
1. Replace namespace_lock field:
OLD: pub namespace_lock: Arc<rustfs_lock::NamespaceLock>
NEW: pub fast_lock_manager: Arc<FastObjectLockManager>
2. Replace lock acquisition:
OLD: self.namespace_lock.lock_guard(object, &self.locker_owner, timeout, ttl).await?
NEW: self.fast_lock_manager.acquire_write_lock(bucket, object, &self.locker_owner).await?
3. Replace read lock acquisition:
OLD: self.namespace_lock.rlock_guard(object, &self.locker_owner, timeout, ttl).await?
NEW: self.fast_lock_manager.acquire_read_lock(bucket, object, &self.locker_owner).await?
4. Add version support where needed:
NEW: self.fast_lock_manager.acquire_write_lock_versioned(bucket, object, version, owner).await?
5. Replace batch operations:
OLD: Multiple individual lock_guard calls in loop
NEW: Single BatchLockRequest with all objects
6. Remove manual lock release (RAII handles it automatically)
OLD: guard.disarm() or explicit release
NEW: Just let guard go out of scope
Expected performance improvements:
- 10-50x faster lock acquisition
- 90%+ fast path success rate
- Sub-millisecond lock operations
- No deadlock issues with batch operations
- Automatic cleanup and monitoring
*/
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_integration_example() {
let fast_manager = Arc::new(FastObjectLockManager::new());
let set_disks = SetDisksWithFastLock {
fast_lock_manager: fast_manager,
locker_owner: "test_owner".to_string(),
};
// Test read operation
assert!(set_disks.get_object_reader_fast("bucket", "object", None).await.is_ok());
// Test write operation
assert!(set_disks.put_object_fast("bucket", "object", Some("v1")).await.is_ok());
// Test batch delete
let objects = vec![("obj1", None), ("obj2", Some("v1"))];
let result = set_disks.delete_objects_fast("bucket", objects).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_version_locking() {
let fast_manager = Arc::new(FastObjectLockManager::new());
// Should be able to lock different versions simultaneously
let guard_v1 = fast_manager
.acquire_write_lock_versioned("bucket", "object", "v1", "owner1")
.await
.expect("Failed to lock v1");
let guard_v2 = fast_manager
.acquire_write_lock_versioned("bucket", "object", "v2", "owner2")
.await
.expect("Failed to lock v2");
// Both locks should coexist
assert!(!guard_v1.is_released());
assert!(!guard_v2.is_released());
}
}
@@ -1,166 +0,0 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Integration tests for performance optimizations
#[cfg(test)]
mod tests {
use crate::fast_lock::FastObjectLockManager;
use tokio::time::Duration;
#[tokio::test]
async fn test_object_pool_integration() {
let manager = FastObjectLockManager::new();
// Create many locks to test pool efficiency
let mut guards = Vec::new();
for i in 0..100 {
let bucket = format!("test-bucket-{}", i % 10); // Reuse some bucket names
let object = format!("test-object-{i}");
let guard = manager
.acquire_write_lock(bucket.as_str(), object.as_str(), "test-owner")
.await
.expect("Failed to acquire lock");
guards.push(guard);
}
// Drop all guards to return objects to pool
drop(guards);
// Wait a moment for cleanup
tokio::time::sleep(Duration::from_millis(100)).await;
// Get pool statistics from all shards
let pool_stats = manager.get_pool_stats();
let (hits, misses, releases, pool_size) = pool_stats.iter().fold((0, 0, 0, 0), |acc, stats| {
(acc.0 + stats.0, acc.1 + stats.1, acc.2 + stats.2, acc.3 + stats.3)
});
let hit_rate = if hits + misses > 0 {
hits as f64 / (hits + misses) as f64
} else {
0.0
};
println!("Pool stats - Hits: {hits}, Misses: {misses}, Releases: {releases}, Pool size: {pool_size}");
println!("Hit rate: {:.2}%", hit_rate * 100.0);
// We should see some pool activity
assert!(hits + misses > 0, "Pool should have been used");
}
#[tokio::test]
async fn test_optimized_notification_system() {
let manager = FastObjectLockManager::new();
// Test that notifications work by measuring timing
let start = std::time::Instant::now();
// Acquire two read locks on different objects (should be fast)
let guard1 = manager
.acquire_read_lock("bucket", "object1", "reader1")
.await
.expect("Failed to acquire first read lock");
let guard2 = manager
.acquire_read_lock("bucket", "object2", "reader2")
.await
.expect("Failed to acquire second read lock");
let duration = start.elapsed();
println!("Two read locks on different objects took: {duration:?}");
// Should be very fast since no contention
assert!(duration < Duration::from_millis(10), "Read locks should be fast with no contention");
drop(guard1);
drop(guard2);
// Test same object contention
let start = std::time::Instant::now();
let guard1 = manager
.acquire_read_lock("bucket", "same-object", "reader1")
.await
.expect("Failed to acquire first read lock on same object");
let guard2 = manager
.acquire_read_lock("bucket", "same-object", "reader2")
.await
.expect("Failed to acquire second read lock on same object");
let duration = start.elapsed();
println!("Two read locks on same object took: {duration:?}");
// Should still be fast since read locks are compatible
assert!(duration < Duration::from_millis(10), "Compatible read locks should be fast");
drop(guard1);
drop(guard2);
}
#[tokio::test]
async fn test_fast_path_optimization() {
let manager = FastObjectLockManager::new();
// First acquisition should be fast path
let start = std::time::Instant::now();
let guard1 = manager
.acquire_read_lock("bucket", "object", "reader1")
.await
.expect("Failed to acquire first read lock");
let first_duration = start.elapsed();
// Second read lock should also be fast path
let start = std::time::Instant::now();
let guard2 = manager
.acquire_read_lock("bucket", "object", "reader2")
.await
.expect("Failed to acquire second read lock");
let second_duration = start.elapsed();
println!("First lock: {first_duration:?}, Second lock: {second_duration:?}");
// Both should be very fast (sub-millisecond typically)
assert!(first_duration < Duration::from_millis(10));
assert!(second_duration < Duration::from_millis(10));
drop(guard1);
drop(guard2);
}
#[tokio::test]
async fn test_batch_operations_optimization() {
let manager = FastObjectLockManager::new();
// Test batch operation with sorted keys
let batch = crate::fast_lock::BatchLockRequest::new("batch-owner")
.add_read_lock("bucket", "obj1")
.add_read_lock("bucket", "obj2")
.add_write_lock("bucket", "obj3")
.with_all_or_nothing(false);
let start = std::time::Instant::now();
let result = manager.acquire_locks_batch(batch).await;
let duration = start.elapsed();
println!("Batch operation took: {duration:?}");
assert!(result.all_acquired, "All locks should be acquired");
assert_eq!(result.successful_locks.len(), 3);
assert!(result.failed_locks.is_empty());
// Batch should be reasonably fast
assert!(duration < Duration::from_millis(100));
}
}
+16 -235
View File
@@ -77,100 +77,54 @@ impl FastObjectLockManager {
}
/// Acquire shared (read) lock
pub async fn acquire_read_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(bucket, object, owner);
pub async fn acquire_read_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>>) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(key, owner);
self.acquire_lock(request).await
}
/// Acquire shared (read) lock for specific version
pub async fn acquire_read_lock_versioned(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
version: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_read(bucket, object, owner).with_version(version);
self.acquire_lock(request).await
}
/// Acquire exclusive (write) lock
pub async fn acquire_write_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
// let bucket = bucket.into();
// let object = object.into();
// let owner = owner.into();
// error!("acquire_write_lock: bucket={:?}, object={:?}, owner={:?}", bucket, object, owner);
let request = ObjectLockRequest::new_write(bucket, object, owner);
self.acquire_lock(request).await
}
/// Acquire exclusive (write) lock for specific version
pub async fn acquire_write_lock_versioned(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
version: impl Into<Arc<str>>,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_write(bucket, object, owner).with_version(version);
pub async fn acquire_write_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>>) -> Result<FastLockGuard, LockResult> {
let request = ObjectLockRequest::new_write(key, owner);
self.acquire_lock(request).await
}
/// Acquire high-priority read lock - optimized for database queries
pub async fn acquire_high_priority_read_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
key: ObjectKey,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request =
ObjectLockRequest::new_read(bucket, object, owner).with_priority(crate::fast_lock::types::LockPriority::High);
let request = ObjectLockRequest::new_read(key, owner).with_priority(crate::fast_lock::types::LockPriority::High);
self.acquire_lock(request).await
}
/// Acquire high-priority write lock - optimized for database queries
pub async fn acquire_high_priority_write_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
key: ObjectKey,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request =
ObjectLockRequest::new_write(bucket, object, owner).with_priority(crate::fast_lock::types::LockPriority::High);
let request = ObjectLockRequest::new_write(key, owner).with_priority(crate::fast_lock::types::LockPriority::High);
self.acquire_lock(request).await
}
/// Acquire critical priority read lock - for system operations
pub async fn acquire_critical_read_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
key: ObjectKey,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request =
ObjectLockRequest::new_read(bucket, object, owner).with_priority(crate::fast_lock::types::LockPriority::Critical);
let request = ObjectLockRequest::new_read(key, owner).with_priority(crate::fast_lock::types::LockPriority::Critical);
self.acquire_lock(request).await
}
/// Acquire critical priority write lock - for system operations
pub async fn acquire_critical_write_lock(
&self,
bucket: impl Into<Arc<str>>,
object: impl Into<Arc<str>>,
key: ObjectKey,
owner: impl Into<Arc<str>>,
) -> Result<FastLockGuard, LockResult> {
let request =
ObjectLockRequest::new_write(bucket, object, owner).with_priority(crate::fast_lock::types::LockPriority::Critical);
let request = ObjectLockRequest::new_write(key, owner).with_priority(crate::fast_lock::types::LockPriority::Critical);
self.acquire_lock(request).await
}
@@ -440,42 +394,12 @@ impl LockManager for FastObjectLockManager {
self.acquire_lock(request).await
}
async fn acquire_read_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock(bucket, object, owner).await
async fn acquire_read_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock(key, owner).await
}
async fn acquire_read_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_read_lock_versioned(bucket, object, version, owner).await
}
async fn acquire_write_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock(bucket, object, owner).await
}
async fn acquire_write_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock_versioned(bucket, object, version, owner).await
async fn acquire_write_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult> {
self.acquire_write_lock(key, owner).await
}
async fn acquire_locks_batch(&self, batch_request: BatchLockRequest) -> BatchLockResult {
@@ -514,146 +438,3 @@ impl LockManager for FastObjectLockManager {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::Duration;
#[tokio::test]
async fn test_manager_basic_operations() {
let manager = FastObjectLockManager::new();
// Test read lock
let read_guard = manager
.acquire_read_lock("bucket", "object", "owner1")
.await
.expect("Failed to acquire read lock");
// Should be able to acquire another read lock
let read_guard2 = manager
.acquire_read_lock("bucket", "object", "owner2")
.await
.expect("Failed to acquire second read lock");
drop(read_guard);
drop(read_guard2);
// Test write lock
let write_guard = manager
.acquire_write_lock("bucket", "object", "owner1")
.await
.expect("Failed to acquire write lock");
drop(write_guard);
}
#[tokio::test]
async fn test_manager_contention() {
let manager = Arc::new(FastObjectLockManager::new());
// Acquire write lock
let write_guard = manager
.acquire_write_lock("bucket", "object", "owner1")
.await
.expect("Failed to acquire write lock");
// Try to acquire read lock (should timeout)
let manager_clone = manager.clone();
let read_result =
tokio::time::timeout(Duration::from_millis(100), manager_clone.acquire_read_lock("bucket", "object", "owner2")).await;
assert!(read_result.is_err()); // Should timeout
drop(write_guard);
// Now read lock should succeed
let read_guard = manager
.acquire_read_lock("bucket", "object", "owner2")
.await
.expect("Failed to acquire read lock after write lock released");
drop(read_guard);
}
#[tokio::test]
async fn test_versioned_locks() {
let manager = FastObjectLockManager::new();
// Acquire lock on version v1
let v1_guard = manager
.acquire_write_lock_versioned("bucket", "object", "v1", "owner1")
.await
.expect("Failed to acquire v1 lock");
// Should be able to acquire lock on version v2 simultaneously
let v2_guard = manager
.acquire_write_lock_versioned("bucket", "object", "v2", "owner2")
.await
.expect("Failed to acquire v2 lock");
drop(v1_guard);
drop(v2_guard);
}
#[tokio::test]
async fn test_batch_operations() {
let manager = FastObjectLockManager::new();
let batch = BatchLockRequest::new("owner")
.add_read_lock("bucket", "obj1")
.add_write_lock("bucket", "obj2")
.with_all_or_nothing(true);
let result = manager.acquire_locks_batch(batch).await;
assert!(result.all_acquired);
assert_eq!(result.successful_locks.len(), 2);
assert!(result.failed_locks.is_empty());
}
#[tokio::test]
async fn test_metrics() {
let manager = FastObjectLockManager::new();
// Perform some operations
let _guard1 = manager.acquire_read_lock("bucket", "obj1", "owner").await.unwrap();
let _guard2 = manager.acquire_write_lock("bucket", "obj2", "owner").await.unwrap();
let metrics = manager.get_metrics();
assert!(metrics.shard_metrics.total_acquisitions() > 0);
assert!(metrics.shard_metrics.fast_path_rate() > 0.0);
}
#[tokio::test]
async fn test_cleanup() {
let config = LockConfig {
max_idle_time: Duration::from_secs(1), // Use 1 second for easier testing
..Default::default()
};
let manager = FastObjectLockManager::with_config(config);
// Acquire and release some locks
{
let _guard = manager.acquire_read_lock("bucket", "obj1", "owner1").await.unwrap();
let _guard2 = manager.acquire_read_lock("bucket", "obj2", "owner2").await.unwrap();
} // Locks are released here
// Check lock count before cleanup
let count_before = manager.total_lock_count();
assert!(count_before >= 2, "Should have at least 2 locks before cleanup");
// Wait for idle timeout
tokio::time::sleep(Duration::from_secs(2)).await;
// Force cleanup with traditional method to ensure cleanup for testing
let cleaned = manager.cleanup_expired_traditional().await;
let count_after = manager.total_lock_count();
// The test should pass if cleanup works at all
assert!(
cleaned > 0 || count_after < count_before,
"Cleanup should either clean locks or they should be cleaned by other means"
);
}
}
+2 -30
View File
@@ -31,38 +31,10 @@ pub trait LockManager: Send + Sync {
async fn acquire_lock(&self, request: ObjectLockRequest) -> Result<FastLockGuard, LockResult>;
/// Acquire shared (read) lock
async fn acquire_read_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult>;
/// Acquire shared (read) lock for specific version
async fn acquire_read_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult>;
async fn acquire_read_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult>;
/// Acquire exclusive (write) lock
async fn acquire_write_lock(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult>;
/// Acquire exclusive (write) lock for specific version
async fn acquire_write_lock_versioned(
&self,
bucket: impl Into<Arc<str>> + Send,
object: impl Into<Arc<str>> + Send,
version: impl Into<Arc<str>> + Send,
owner: impl Into<Arc<str>> + Send,
) -> Result<FastLockGuard, LockResult>;
async fn acquire_write_lock(&self, key: ObjectKey, owner: impl Into<Arc<str>> + Send) -> Result<FastLockGuard, LockResult>;
/// Acquire multiple locks atomically
async fn acquire_locks_batch(&self, batch_request: BatchLockRequest) -> BatchLockResult;
+9 -8
View File
@@ -26,8 +26,6 @@
pub mod disabled_manager;
pub mod guard;
pub mod integration_example;
pub mod integration_test;
pub mod manager;
pub mod manager_trait;
pub mod metrics;
@@ -37,6 +35,9 @@ pub mod shard;
pub mod state;
pub mod types;
#[cfg(test)]
mod tests;
// Re-export main types
pub use disabled_manager::DisabledLockManager;
pub use guard::FastLockGuard;
@@ -45,19 +46,19 @@ pub use manager_trait::LockManager;
use std::time::Duration;
pub use types::*;
/// Default RustFS specific timeouts in seconds
pub(crate) const DEFAULT_RUSTFS_MAX_ACQUIRE_TIMEOUT: u64 = 120;
/// Maximum acquire timeout in seconds (for slow storage / high contention; override via env)
pub(crate) const DEFAULT_RUSTFS_MAX_ACQUIRE_TIMEOUT: u64 = 60;
/// Default RustFS acquire timeout in seconds
pub(crate) const DEFAULT_RUSTFS_ACQUIRE_TIMEOUT: u64 = 60;
/// Default acquire timeout in seconds (how long to wait for a lock before giving up)
pub(crate) const DEFAULT_RUSTFS_ACQUIRE_TIMEOUT: u64 = 10;
/// Default shard count (must be power of 2)
pub const DEFAULT_SHARD_COUNT: usize = 1024;
/// Default lock timeout
/// Default lock timeout (lease TTL; lock is released if not refreshed within this duration)
pub const DEFAULT_LOCK_TIMEOUT: Duration = Duration::from_secs(30);
/// Default acquire timeout - increased for network block storage workloads (e.g., Hetzner Ceph)
/// Default acquire timeout - common value for local/low-latency; use env to increase for slow storage
pub const DEFAULT_ACQUIRE_TIMEOUT: Duration = Duration::from_secs(DEFAULT_RUSTFS_ACQUIRE_TIMEOUT);
/// Maximum acquire timeout for high-load scenarios
+8 -4
View File
@@ -759,13 +759,17 @@ mod tests {
let shard = LockShard::new(0);
// First acquire a lock that will block the batch operation
let blocking_request = ObjectLockRequest::new_write("bucket", "obj1", "blocking_owner");
let blocking_request = ObjectLockRequest::new_write(ObjectKey::new("bucket", "obj1"), "blocking_owner")
.with_acquire_timeout(Duration::from_secs(1));
shard.acquire_lock(&blocking_request).await.unwrap();
// Now try a batch operation that should fail and clean up properly
// Use short acquire timeout so the test fails fast when obj1 is already locked
// (default is 60s which would make this test very slow)
let requests = vec![
ObjectLockRequest::new_read("bucket", "obj2", "batch_owner"), // This should succeed
ObjectLockRequest::new_write("bucket", "obj1", "batch_owner"), // This should fail due to existing lock
ObjectLockRequest::new_read(ObjectKey::new("bucket", "obj2"), "batch_owner")
.with_acquire_timeout(Duration::from_millis(100)), // This should succeed
ObjectLockRequest::new_write(ObjectKey::new("bucket", "obj1"), "batch_owner")
.with_acquire_timeout(Duration::from_millis(100)), // This should fail due to existing lock
];
let result = shard.acquire_locks_batch(requests, true).await;
+532
View File
@@ -0,0 +1,532 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(test)]
mod fast_lock_tests {
use crate::fast_lock::FastObjectLockManager;
use crate::fast_lock::types::{LockConfig, LockMode, LockPriority, LockResult, ObjectKey, ObjectLockRequest};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::sleep;
/// Helper function to create a test lock manager
fn create_test_manager() -> FastObjectLockManager {
let config = LockConfig {
shard_count: 4, // Use smaller shard count for tests
default_lock_timeout: Duration::from_secs(30),
default_acquire_timeout: Duration::from_secs(5),
..LockConfig::default()
};
FastObjectLockManager::with_config(config)
}
#[tokio::test]
async fn test_basic_write_lock_acquire_release() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner: Arc<str> = Arc::from("test-owner");
// Acquire write lock
let mut guard = manager
.acquire_write_lock(key.clone(), owner.clone())
.await
.expect("Should acquire write lock");
// Verify guard properties
assert_eq!(guard.key(), &key);
assert_eq!(guard.mode(), LockMode::Exclusive);
assert_eq!(guard.owner(), &owner);
assert!(!guard.is_released());
// Manually release lock
assert!(guard.release(), "Should release lock successfully");
assert!(guard.is_released(), "Guard should be marked as released");
// Try to acquire again - should succeed
let guard2 = manager
.acquire_write_lock(key.clone(), owner.clone())
.await
.expect("Should acquire write lock again after release");
drop(guard2);
}
#[tokio::test]
async fn test_basic_read_lock_acquire_release() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner: Arc<str> = Arc::from("test-owner");
// Acquire read lock
let mut guard = manager
.acquire_read_lock(key.clone(), owner.clone())
.await
.expect("Should acquire read lock");
// Verify guard properties
assert_eq!(guard.key(), &key);
assert_eq!(guard.mode(), LockMode::Shared);
assert_eq!(guard.owner(), &owner);
assert!(!guard.is_released());
// Manually release lock
assert!(guard.release(), "Should release lock successfully");
assert!(guard.is_released(), "Guard should be marked as released");
}
#[tokio::test]
async fn test_lock_auto_release_on_drop() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner1: Arc<str> = Arc::from("owner1");
let owner2: Arc<str> = Arc::from("owner2");
// Acquire lock and drop guard
{
let guard = manager
.acquire_write_lock(key.clone(), owner1.clone())
.await
.expect("Should acquire write lock");
assert!(!guard.is_released());
// Guard is dropped here, lock should be automatically released
}
// Wait a bit to ensure cleanup
sleep(Duration::from_millis(10)).await;
// Another owner should be able to acquire the lock
let guard2 = manager
.acquire_write_lock(key.clone(), owner2.clone())
.await
.expect("Should acquire write lock after previous guard dropped");
drop(guard2);
}
#[tokio::test]
async fn test_multiple_read_locks() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner1: Arc<str> = Arc::from("owner1");
let owner2: Arc<str> = Arc::from("owner2");
let owner3: Arc<str> = Arc::from("owner3");
// Multiple read locks should be allowed
let mut guard1 = manager
.acquire_read_lock(key.clone(), owner1.clone())
.await
.expect("Should acquire first read lock");
let mut guard2 = manager
.acquire_read_lock(key.clone(), owner2.clone())
.await
.expect("Should acquire second read lock");
let mut guard3 = manager
.acquire_read_lock(key.clone(), owner3.clone())
.await
.expect("Should acquire third read lock");
// All guards should be valid
assert_eq!(guard1.mode(), LockMode::Shared);
assert_eq!(guard2.mode(), LockMode::Shared);
assert_eq!(guard3.mode(), LockMode::Shared);
// Release all
assert!(guard1.release());
assert!(guard2.release());
assert!(guard3.release());
}
#[tokio::test]
async fn test_write_lock_excludes_read_lock() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let writer: Arc<str> = Arc::from("writer");
let reader: Arc<str> = Arc::from("reader");
// Acquire write lock
let mut write_guard = manager
.acquire_write_lock(key.clone(), writer.clone())
.await
.expect("Should acquire write lock");
// Try to acquire read lock - should timeout
let read_request =
ObjectLockRequest::new_read(key.clone(), reader.clone()).with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(read_request).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"Read lock should timeout when write lock is held"
);
// Release write lock
assert!(write_guard.release());
// Now read lock should succeed
let mut read_guard = manager
.acquire_read_lock(key.clone(), reader.clone())
.await
.expect("Should acquire read lock after write lock released");
assert!(read_guard.release());
}
#[tokio::test]
async fn test_read_lock_excludes_write_lock() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let reader: Arc<str> = Arc::from("reader");
let writer: Arc<str> = Arc::from("writer");
// Acquire read lock
let mut read_guard = manager
.acquire_read_lock(key.clone(), reader.clone())
.await
.expect("Should acquire read lock");
// Try to acquire write lock - should timeout
let write_request =
ObjectLockRequest::new_write(key.clone(), writer.clone()).with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(write_request).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"Write lock should timeout when read lock is held"
);
// Release read lock
assert!(read_guard.release());
// Now write lock should succeed
let mut write_guard = manager
.acquire_write_lock(key.clone(), writer.clone())
.await
.expect("Should acquire write lock after read lock released");
assert!(write_guard.release());
}
#[tokio::test]
async fn test_write_lock_excludes_write_lock() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner1: Arc<str> = Arc::from("owner1");
let owner2: Arc<str> = Arc::from("owner2");
// Acquire first write lock
let mut guard1 = manager
.acquire_write_lock(key.clone(), owner1.clone())
.await
.expect("Should acquire first write lock");
// Try to acquire second write lock - should timeout
let request2 = ObjectLockRequest::new_write(key.clone(), owner2.clone()).with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(request2).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"Second write lock should timeout when first write lock is held"
);
// Release first lock
assert!(guard1.release());
// Now second write lock should succeed
let mut guard2 = manager
.acquire_write_lock(key.clone(), owner2.clone())
.await
.expect("Should acquire second write lock after first released");
assert!(guard2.release());
}
#[tokio::test]
async fn test_same_owner_reentrant_write_lock() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner: Arc<str> = Arc::from("owner");
// Acquire first write lock
let mut guard1 = manager
.acquire_write_lock(key.clone(), owner.clone())
.await
.expect("Should acquire first write lock");
// Same owner trying to acquire again - should timeout (not reentrant)
let request2 = ObjectLockRequest::new_write(key.clone(), owner.clone()).with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(request2).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"Same owner should not be able to acquire lock again (not reentrant)"
);
assert!(guard1.release());
}
#[tokio::test]
async fn test_different_keys_no_conflict() {
let manager = create_test_manager();
let key1 = ObjectKey::new("bucket1", "object1");
let key2 = ObjectKey::new("bucket2", "object2");
let owner: Arc<str> = Arc::from("owner");
// Acquire locks on different keys simultaneously
let mut guard1 = manager
.acquire_write_lock(key1.clone(), owner.clone())
.await
.expect("Should acquire lock on key1");
let mut guard2 = manager
.acquire_write_lock(key2.clone(), owner.clone())
.await
.expect("Should acquire lock on key2");
// Both should be valid
assert_eq!(guard1.key(), &key1);
assert_eq!(guard2.key(), &key2);
assert!(guard1.release());
assert!(guard2.release());
}
#[tokio::test]
async fn test_versioned_keys() {
let manager = create_test_manager();
let base_key = ObjectKey::new("bucket", "object");
let versioned_key = ObjectKey::with_version("bucket", "object", "v1");
let owner: Arc<str> = Arc::from("owner");
// Acquire lock on base key
let mut guard1 = manager
.acquire_write_lock(base_key.clone(), owner.clone())
.await
.expect("Should acquire lock on base key");
// Should be able to acquire lock on versioned key (different keys)
let mut guard2 = manager
.acquire_write_lock(versioned_key.clone(), owner.clone())
.await
.expect("Should acquire lock on versioned key");
assert_eq!(guard1.key(), &base_key);
assert_eq!(guard2.key(), &versioned_key);
assert!(guard1.release());
assert!(guard2.release());
}
#[tokio::test]
async fn test_concurrent_read_locks() {
let manager = Arc::new(create_test_manager());
let key = ObjectKey::new("test-bucket", "test-object");
let num_readers = 10;
let mut handles = Vec::new();
// Spawn multiple readers
for i in 0..num_readers {
let manager = manager.clone();
let key = key.clone();
let owner: Arc<str> = Arc::from(format!("reader-{}", i));
let handle = tokio::spawn(async move {
let mut guard = manager.acquire_read_lock(key, owner).await.expect("Should acquire read lock");
// Hold lock for a bit
sleep(Duration::from_millis(10)).await;
assert!(guard.release());
});
handles.push(handle);
}
// Wait for all readers
for handle in handles {
handle.await.expect("Reader task should complete");
}
}
#[tokio::test]
async fn test_concurrent_write_lock_contention() {
let manager = Arc::new(create_test_manager());
let key = ObjectKey::new("test-bucket", "test-object");
let num_writers = 5;
let mut handles = Vec::new();
// Spawn multiple writers - they should serialize
for i in 0..num_writers {
let manager = manager.clone();
let key = key.clone();
let owner: Arc<str> = Arc::from(format!("writer-{}", i));
let handle = tokio::spawn(async move {
let mut guard = manager
.acquire_write_lock(key, owner)
.await
.expect("Should acquire write lock");
// Hold lock for a bit
sleep(Duration::from_millis(10)).await;
assert!(guard.release());
});
handles.push(handle);
}
// Wait for all writers - they should complete sequentially
for handle in handles {
handle.await.expect("Writer task should complete");
}
}
#[tokio::test]
async fn test_lock_timeout() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner1: Arc<str> = Arc::from("owner1");
let owner2: Arc<str> = Arc::from("owner2");
// Acquire first lock
let mut guard1 = manager
.acquire_write_lock(key.clone(), owner1.clone())
.await
.expect("Should acquire first lock");
// Try to acquire with short timeout - should timeout
let request = ObjectLockRequest::new_write(key.clone(), owner2.clone()).with_acquire_timeout(Duration::from_millis(50));
let result = manager.acquire_lock(request).await;
assert!(matches!(result, Err(LockResult::Timeout)), "Should timeout when lock is held");
assert!(guard1.release());
}
#[tokio::test]
async fn test_lock_priority() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let normal_owner: Arc<str> = Arc::from("normal");
let high_owner: Arc<str> = Arc::from("high");
// Acquire normal priority lock
let normal_request = ObjectLockRequest::new_write(key.clone(), normal_owner.clone())
.with_priority(LockPriority::Normal)
.with_acquire_timeout(Duration::from_secs(1));
let mut normal_guard = manager
.acquire_lock(normal_request)
.await
.expect("Should acquire normal priority lock");
// Try high priority lock - should still timeout (write locks are exclusive)
let high_request = ObjectLockRequest::new_write(key.clone(), high_owner.clone())
.with_priority(LockPriority::High)
.with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(high_request).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"High priority write lock should still timeout when normal write lock is held"
);
assert!(normal_guard.release());
}
#[tokio::test]
async fn test_double_release() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner: Arc<str> = Arc::from("owner");
let mut guard = manager
.acquire_write_lock(key.clone(), owner.clone())
.await
.expect("Should acquire lock");
// First release should succeed
assert!(guard.release(), "First release should succeed");
assert!(guard.is_released(), "Guard should be marked as released");
// Second release should fail
assert!(!guard.release(), "Second release should fail");
}
#[tokio::test]
async fn test_lock_info() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let owner: Arc<str> = Arc::from("owner");
let mut guard = manager
.acquire_write_lock(key.clone(), owner.clone())
.await
.expect("Should acquire lock");
// Get lock info
let lock_info = guard.lock_info();
assert!(lock_info.is_some(), "Should have lock info");
if let Some(info) = lock_info {
assert_eq!(info.key, key);
assert_eq!(info.mode, LockMode::Exclusive);
assert_eq!(info.owner, owner);
}
// Release lock
assert!(guard.release());
// Lock info should be None after release
let lock_info_after = guard.lock_info();
assert!(lock_info_after.is_none(), "Lock info should be None after release");
}
#[tokio::test]
async fn test_read_write_mixed_scenario() {
let manager = create_test_manager();
let key = ObjectKey::new("test-bucket", "test-object");
let reader1: Arc<str> = Arc::from("reader1");
let reader2: Arc<str> = Arc::from("reader2");
let writer: Arc<str> = Arc::from("writer");
// Acquire two read locks
let mut read_guard1 = manager
.acquire_read_lock(key.clone(), reader1.clone())
.await
.expect("Should acquire first read lock");
let mut read_guard2 = manager
.acquire_read_lock(key.clone(), reader2.clone())
.await
.expect("Should acquire second read lock");
// Writer should timeout
let write_request =
ObjectLockRequest::new_write(key.clone(), writer.clone()).with_acquire_timeout(Duration::from_millis(100));
let result = manager.acquire_lock(write_request).await;
assert!(
matches!(result, Err(LockResult::Timeout)),
"Write lock should timeout when read locks are held"
);
// Release one read lock
assert!(read_guard1.release());
// Writer should still timeout (other read lock still held)
let write_request2 =
ObjectLockRequest::new_write(key.clone(), writer.clone()).with_acquire_timeout(Duration::from_millis(100));
let result2 = manager.acquire_lock(write_request2).await;
assert!(
matches!(result2, Err(LockResult::Timeout)),
"Write lock should still timeout when read lock is held"
);
// Release second read lock
assert!(read_guard2.release());
// Now writer should succeed
let mut write_guard = manager
.acquire_write_lock(key.clone(), writer.clone())
.await
.expect("Should acquire write lock after all read locks released");
assert!(write_guard.release());
}
}
+94 -14
View File
@@ -13,7 +13,7 @@
// limitations under the License.
use crate::fast_lock::guard::FastLockGuard;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use smartstring::SmartString;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
@@ -28,6 +28,88 @@ pub struct ObjectKey {
pub version: Option<Arc<str>>, // None means latest version
}
impl Serialize for ObjectKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("ObjectKey", 3)?;
state.serialize_field("bucket", self.bucket.as_ref())?;
state.serialize_field("object", self.object.as_ref())?;
state.serialize_field("version", &self.version.as_ref().map(|v| v.as_ref()))?;
state.end()
}
}
impl<'de> Deserialize<'de> for ObjectKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, MapAccess, Visitor};
use std::fmt;
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Bucket,
Object,
Version,
}
struct ObjectKeyVisitor;
impl<'de> Visitor<'de> for ObjectKeyVisitor {
type Value = ObjectKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct ObjectKey")
}
fn visit_map<V>(self, mut map: V) -> Result<ObjectKey, V::Error>
where
V: MapAccess<'de>,
{
let mut bucket = None;
let mut object = None;
let mut version = None;
while let Some(key) = map.next_key()? {
match key {
Field::Bucket => {
if bucket.is_some() {
return Err(de::Error::duplicate_field("bucket"));
}
let s: String = map.next_value()?;
bucket = Some(Arc::from(s));
}
Field::Object => {
if object.is_some() {
return Err(de::Error::duplicate_field("object"));
}
let s: String = map.next_value()?;
object = Some(Arc::from(s));
}
Field::Version => {
if version.is_some() {
return Err(de::Error::duplicate_field("version"));
}
let opt: Option<String> = map.next_value()?;
version = opt.map(Arc::from);
}
}
}
let bucket = bucket.ok_or_else(|| de::Error::missing_field("bucket"))?;
let object = object.ok_or_else(|| de::Error::missing_field("object"))?;
Ok(ObjectKey { bucket, object, version })
}
}
const FIELDS: &[&str] = &["bucket", "object", "version"];
deserializer.deserialize_struct("ObjectKey", FIELDS, ObjectKeyVisitor)
}
}
impl ObjectKey {
pub fn new(bucket: impl Into<Arc<str>>, object: impl Into<Arc<str>>) -> Self {
Self {
@@ -198,9 +280,9 @@ pub struct ObjectLockRequest {
}
impl ObjectLockRequest {
pub fn new_read(bucket: impl Into<Arc<str>>, object: impl Into<Arc<str>>, owner: impl Into<Arc<str>>) -> Self {
pub fn new_read(key: ObjectKey, owner: impl Into<Arc<str>>) -> Self {
Self {
key: ObjectKey::new(bucket, object),
key,
mode: LockMode::Shared,
owner: owner.into(),
acquire_timeout: crate::fast_lock::DEFAULT_ACQUIRE_TIMEOUT,
@@ -209,9 +291,9 @@ impl ObjectLockRequest {
}
}
pub fn new_write(bucket: impl Into<Arc<str>>, object: impl Into<Arc<str>>, owner: impl Into<Arc<str>>) -> Self {
pub fn new_write(key: ObjectKey, owner: impl Into<Arc<str>>) -> Self {
Self {
key: ObjectKey::new(bucket, object),
key,
mode: LockMode::Exclusive,
owner: owner.into(),
acquire_timeout: crate::fast_lock::DEFAULT_ACQUIRE_TIMEOUT,
@@ -317,15 +399,13 @@ impl BatchLockRequest {
}
}
pub fn add_read_lock(mut self, bucket: impl Into<Arc<str>>, object: impl Into<Arc<str>>) -> Self {
self.requests
.push(ObjectLockRequest::new_read(bucket, object, self.owner.clone()));
pub fn add_read_lock(mut self, key: ObjectKey) -> Self {
self.requests.push(ObjectLockRequest::new_read(key, self.owner.clone()));
self
}
pub fn add_write_lock(mut self, bucket: impl Into<Arc<str>>, object: impl Into<Arc<str>>) -> Self {
self.requests
.push(ObjectLockRequest::new_write(bucket, object, self.owner.clone()));
pub fn add_write_lock(mut self, key: ObjectKey) -> Self {
self.requests.push(ObjectLockRequest::new_write(key, self.owner.clone()));
self
}
@@ -366,7 +446,7 @@ mod tests {
#[test]
fn test_lock_request() {
let req = ObjectLockRequest::new_read("bucket", "object", "owner")
let req = ObjectLockRequest::new_read(ObjectKey::new("bucket", "object"), "owner")
.with_version("v1")
.with_priority(LockPriority::High);
@@ -378,8 +458,8 @@ mod tests {
#[test]
fn test_batch_request() {
let batch = BatchLockRequest::new("owner")
.add_read_lock("bucket", "obj1")
.add_write_lock("bucket", "obj2");
.add_read_lock(ObjectKey::new("bucket", "obj1"))
.add_write_lock(ObjectKey::new("bucket", "obj2"));
assert_eq!(batch.requests.len(), 2);
assert_eq!(batch.requests[0].mode, LockMode::Shared);