mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 07:06:53 +00:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user