367 lines
9.7 KiB
Rust
367 lines
9.7 KiB
Rust
// Copyright 2024 tison <wander4096@gmail.com>
|
|
//
|
|
// 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.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_try_lock_never_blocks() {
|
|
// Test that try_lock and try_lock_owned never block, even under contention
|
|
let mutex = Arc::new(Mutex::new(9));
|
|
let _guard = mutex.try_lock().unwrap();
|
|
|
|
let result = mutex.try_lock();
|
|
assert!(result.is_none());
|
|
|
|
let result = mutex.clone().try_lock_owned();
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_mut_provides_exclusive_access() {
|
|
// Test that get_mut provides direct access when we have exclusive ownership
|
|
let mut mutex = Mutex::new(11);
|
|
|
|
let data = mutex.get_mut();
|
|
*data = 100;
|
|
|
|
assert_eq!(*mutex.get_mut(), 100);
|
|
|
|
let inner = mutex.into_inner();
|
|
assert_eq!(inner, 100);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_guard_map_preserves_lock() {
|
|
let data = (99i32, vec![1, 2, 3]);
|
|
let mutex = Mutex::new(data);
|
|
|
|
let guard = mutex.lock().await;
|
|
let mut mapped_guard = MutexGuard::map(guard, |data| &mut data.0);
|
|
|
|
assert!(mutex.try_lock().is_none());
|
|
|
|
*mapped_guard = 100;
|
|
|
|
// After dropping, mutex should be available
|
|
drop(mapped_guard);
|
|
let guard = mutex.try_lock().unwrap();
|
|
assert_eq!(guard.0, 100);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mapped_guard_holds_lock() {
|
|
// Test that MappedMutexGuard properly holds the lock even after the original guard is moved
|
|
let mutex = Arc::new(Mutex::new((10, 20)));
|
|
|
|
let guard = mutex.lock().await;
|
|
let mapped_guard = MutexGuard::map(guard, |data| &mut data.0);
|
|
|
|
assert!(
|
|
mutex.try_lock().is_none(),
|
|
"Lock should be held by the mapped guard"
|
|
);
|
|
|
|
assert_eq!(*mapped_guard, 10);
|
|
|
|
drop(mapped_guard);
|
|
|
|
assert!(
|
|
mutex.try_lock().is_some(),
|
|
"Lock should be released after mapped guard is dropped"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_owned_mapped_guard_holds_lock() {
|
|
// Test that mapped owned guard properly holds the lock
|
|
let mutex = Arc::new(Mutex::new((30, 40)));
|
|
|
|
let owned_guard = mutex.clone().lock_owned().await;
|
|
let mapped_owned_guard = OwnedMutexGuard::map(owned_guard, |data| &mut data.1);
|
|
|
|
assert!(
|
|
mutex.try_lock().is_none(),
|
|
"Lock should be held by the mapped owned guard"
|
|
);
|
|
|
|
assert_eq!(*mapped_owned_guard, 40);
|
|
|
|
// When mapped owned guard is dropped, lock should be released
|
|
drop(mapped_owned_guard);
|
|
|
|
assert!(
|
|
mutex.try_lock().is_some(),
|
|
"Lock should be released after mapped owned guard is dropped"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_guard_filter_map_failure() {
|
|
let data: Vec<i32> = vec![];
|
|
let mutex = Mutex::new(data);
|
|
let guard = mutex.lock().await;
|
|
|
|
let result = MutexGuard::filter_map(guard, |vec| vec.get_mut(0));
|
|
assert!(result.is_err());
|
|
|
|
if let Err(mut original_guard) = result {
|
|
original_guard.push(100);
|
|
assert_eq!(*original_guard, vec![100]);
|
|
} else {
|
|
panic!("Expected Err, but got Ok");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_owned_guard_filter_map_failure() {
|
|
let data: Vec<i32> = vec![];
|
|
let mutex = Arc::new(Mutex::new(data));
|
|
let guard = mutex.clone().lock_owned().await;
|
|
|
|
let result = OwnedMutexGuard::filter_map(guard, |vec| vec.get_mut(0));
|
|
assert!(result.is_err());
|
|
|
|
if let Err(mut original_guard) = result {
|
|
original_guard.push(200);
|
|
assert_eq!(*original_guard, vec![200]);
|
|
} else {
|
|
panic!("Expected Err, but got Ok");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_map_operations() {
|
|
// Test multiple consecutive map operations
|
|
let data = vec![vec![1, 2], vec![3, 4]];
|
|
let mutex = Mutex::new(data);
|
|
|
|
let guard = mutex.lock().await;
|
|
let first_vec = MutexGuard::map(guard, |data| &mut data[0]);
|
|
let mut first_element = MappedMutexGuard::map(first_vec, |vec| &mut vec[0]);
|
|
|
|
*first_element = 100;
|
|
drop(first_element);
|
|
|
|
let guard = mutex.lock().await;
|
|
assert_eq!(guard[0][0], 100);
|
|
assert_eq!(guard[0][1], 2);
|
|
assert_eq!(guard[1][0], 3);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_stress() {
|
|
let mutex = Arc::new(Mutex::new(0));
|
|
let mut handles = Vec::new();
|
|
|
|
// Create many concurrent tasks
|
|
for i in 0..1000 {
|
|
let mutex = mutex.clone();
|
|
handles.push(tokio::spawn(async move {
|
|
let mut guard = mutex.lock().await;
|
|
*guard += 1;
|
|
if i % 10 == 0 {
|
|
tokio::task::yield_now().await;
|
|
}
|
|
}));
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
|
|
let final_value = *mutex.lock().await;
|
|
assert_eq!(final_value, 1000);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_guard_prevents_concurrent_access() {
|
|
// Test that holding a guard prevents other tasks from acquiring the lock
|
|
let mutex = Arc::new(Mutex::new(0));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let guard = mutex.lock().await;
|
|
|
|
assert!(
|
|
mutex.try_lock().is_none(),
|
|
"Lock should be held by the first guard"
|
|
);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let _guard2 = mutex_clone.lock().await;
|
|
123
|
|
});
|
|
|
|
tokio::task::yield_now().await;
|
|
|
|
assert!(
|
|
mutex.try_lock().is_none(),
|
|
"Lock should still be held after yielding"
|
|
);
|
|
|
|
drop(guard);
|
|
|
|
let result = handle.await.unwrap();
|
|
assert_eq!(result, 123);
|
|
|
|
assert!(
|
|
mutex.try_lock().is_some(),
|
|
"Lock should be available after all guards are dropped"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lock_panic_safety() {
|
|
use std::panic::AssertUnwindSafe;
|
|
|
|
let mutex = Arc::new(Mutex::new(0));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let result = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
|
let _guard = mutex_clone.try_lock().unwrap();
|
|
panic!("test panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
// Lock should be released after panic
|
|
assert!(mutex.try_lock().is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_async_lock_panic_safety() {
|
|
// Test panic safety with async locks
|
|
let mutex = Arc::new(Mutex::new(0));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let _guard = mutex_clone.lock().await;
|
|
panic!("async test panic");
|
|
});
|
|
|
|
// panic
|
|
assert!(handle.await.is_err());
|
|
|
|
let guard = mutex.try_lock();
|
|
assert!(guard.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_owned_guard_panic_safety() {
|
|
let mutex = Arc::new(Mutex::new(0));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let _guard = mutex_clone.clone().lock_owned().await;
|
|
panic!("owned guard panic");
|
|
});
|
|
|
|
assert!(handle.await.is_err());
|
|
|
|
// Lock should be available after the panicked task
|
|
let guard = mutex.try_lock();
|
|
assert!(guard.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mapped_guard_panic_safety() {
|
|
// Test panic safety with mapped guards
|
|
let mutex = Arc::new(Mutex::new((66, vec![1, 2, 3])));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let guard = mutex_clone.lock().await;
|
|
let _mapped = MutexGuard::map(guard, |data| &mut data.0);
|
|
panic!("mapped guard panic");
|
|
});
|
|
|
|
assert!(handle.await.is_err());
|
|
|
|
let guard = mutex.try_lock();
|
|
assert!(guard.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_memory_ordering_correctness() {
|
|
// Test that mutex provides proper memory ordering guarantees
|
|
// When one task modifies data under mutex protection,
|
|
// another task should see the modification after acquiring the lock
|
|
let mutex = Arc::new(Mutex::new(vec![1, 2, 3]));
|
|
let mutex_clone = mutex.clone();
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let mut guard = mutex_clone.lock().await;
|
|
guard.push(4);
|
|
guard[0] = 100;
|
|
// Lock is released when guard is dropped
|
|
});
|
|
|
|
handle.await.unwrap();
|
|
|
|
let guard = mutex.lock().await;
|
|
assert_eq!(*guard, vec![100, 2, 3, 4]);
|
|
// This test relies on mutex's acquire-release semantics to ensure
|
|
// that modifications made in the critical section are visible
|
|
// to subsequent lock acquisitions
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mutex_zst() {
|
|
// Test that Mutex works correctly with Zero-Sized Types
|
|
let mutex = Arc::new(Mutex::new(()));
|
|
|
|
let mutex_clone = mutex.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let guard = mutex_clone.lock().await;
|
|
*guard;
|
|
});
|
|
|
|
handle.await.unwrap();
|
|
|
|
// try_lock and owned guard should also work with ZST
|
|
let _owned_guard = mutex.clone().lock_owned().await;
|
|
assert!(mutex.try_lock().is_none());
|
|
|
|
drop(_owned_guard);
|
|
let guard = mutex.try_lock().unwrap();
|
|
*guard;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mapped_mutex_guard_send() {
|
|
// Test that MappedMutexGuard can be sent across await points
|
|
#[derive(Debug)]
|
|
struct TestStruct {
|
|
field1: i32,
|
|
_field2: String,
|
|
}
|
|
|
|
let mutex = Arc::new(Mutex::new(TestStruct {
|
|
field1: 2,
|
|
_field2: "oh".to_owned(),
|
|
}));
|
|
|
|
let mutex_clone = mutex.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let guard = mutex_clone.lock().await;
|
|
let mapped_guard = MutexGuard::map(guard, |data| &mut data.field1);
|
|
|
|
tokio::task::yield_now().await;
|
|
*mapped_guard
|
|
});
|
|
|
|
let result = handle.await.unwrap();
|
|
assert_eq!(result, 2);
|
|
}
|