mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-23 20:59:05 +00:00
acce8b2253
* fix(lock): let waiters hear releases and let acquisition succeed past registered waiters Same-key write contention scaled superlinearly with writer count: 8 concurrent conditional PUTs on one key cost ~340-460 ms, 16 cost ~700 ms, 32 cost ~5 s, against ~4 ms per uncontended write and ~10 ms actual lock holds (measured via RUSTFS_OBJECT_LOCK_DIAG at 1 ms thresholds). Outcomes were always correct; the cost was pure waiting. Two coupled defects in fast_lock caused it: 1. The slow path's early retries slept without subscribing to anything. notify_writer()/notify_readers() are gated on the waiter counters, which a sleeper never increments, so a release during the backoff woke nobody. The lock sat free while every loser slept out its full backoff, and the ladder compounded: successive acquires landed at the cumulative ladder offsets (10+20+40+80+100... ms). 2. try_acquire_exclusive demanded the entire packed state word be zero, including the readers_waiting/writers_waiting counter bits. A lock with registered waiters could be acquired by no one - including the waiters themselves, each blocked by the others' registration - so contended acquisition only succeeded in windows where every waiter happened to be unregistered. This is also why (1) could not be fixed by simply registering the sleepers: registration alone deadlocks acquisition until the acquire deadline. try_acquire_shared already masks correctly and preserves the counter bits in its CAS; the exclusive path now mirrors it. The fix: mask the acquisition CAS to ownership bits only (writer flag, active readers), and turn the early-retry sleep into a notification wait bounded by the same backoff, so a release wakes a waiter immediately while the bound still protects against lost or stolen wakeups exactly as NOTIFY_WAIT_CAP does for the post-retry wait. With both changes, 8 concurrent same-key CAS writers resolve in 17-29 ms (was 340-460 ms) and 32 resolve in 20-53 ms (was ~5 s), with per-racer cost now decreasing in N. Outcomes remain exactly one winner, N-1 precondition failures, zero errors at every width. cargo test -p rustfs-lock passes 113/113 at pristine-parity runtime, including test_concurrent_write_lock_contention, which previously only passed because sleepers were invisible to it. * test(lock): pin both halves of the waiter-starvation fix The fix commit touched only production files, so reverting either half left the suite green: test_concurrent_write_lock_contention only waits for five writers to finish and never asserts that acquisition happens before the backoff ladder runs out. Three tests, one per revert: * exclusive_acquisition_ignores_registered_waiters (state.rs) - a free lock with registered waiters must be acquirable, and the CAS must preserve the counters. Fails against the all-zero `expected`. * early_retry_registers_as_waiter (shard.rs) - a waiter in the early-retry backoff must appear in the writer waiter count within the ~750ms early-retry phase, since notify_writer/notify_readers are gated on those counters. Fails against a bare `sleep`, which registers nowhere. * contended_writers_drain_promptly_after_release (tests.rs) - 16 same-key writers, all registered behind one holder, must drain within 1s of the release rather than sit out their 5s acquire deadlines. Fails against the all-zero `expected` end to end. Wakeup latency is deliberately not asserted anywhere. NOTIFY_POOL is a process-global of 128 Notify slots shared by every lock, so a waiter in a concurrently-running test can consume another's notify_one and push it to the end of its rung: a 24-key latency probe measured ~150us in isolation and ~92ms - a full unexpired rung - alongside the existing 64-key missed-wakeup test. That is the stolen wakeup NOTIFY_WAIT_CAP already exists to bound, and it makes any in-suite latency budget flaky. cargo test -p rustfs-lock: 116/116. Signed-off-by: Miguel Amador <miguel@amador.one> --------- Signed-off-by: Miguel Amador <miguel@amador.one>
612 lines
20 KiB
Rust
612 lines
20 KiB
Rust
// 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.
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::{Duration, SystemTime};
|
|
use tokio::sync::Notify;
|
|
|
|
use crate::fast_lock::optimized_notify::OptimizedNotify;
|
|
use crate::fast_lock::types::{LockMode, LockPriority};
|
|
|
|
/// Optimized atomic lock state encoding in u64
|
|
/// Bits: [63:48] reserved | [47:32] writers_waiting | [31:16] readers_waiting | [15:8] readers_count | [7:1] flags | [0] writer_flag
|
|
const WRITER_FLAG_MASK: u64 = 0x1;
|
|
const READERS_SHIFT: u8 = 8;
|
|
const READERS_MASK: u64 = 0xFF << READERS_SHIFT; // Support up to 255 concurrent readers
|
|
const READERS_WAITING_SHIFT: u8 = 16;
|
|
const READERS_WAITING_MASK: u64 = 0xFFFF << READERS_WAITING_SHIFT;
|
|
const WRITERS_WAITING_SHIFT: u8 = 32;
|
|
const WRITERS_WAITING_MASK: u64 = 0xFFFF << WRITERS_WAITING_SHIFT;
|
|
|
|
// Fast path check masks
|
|
const NO_WRITER_AND_NO_WAITING_WRITERS: u64 = WRITER_FLAG_MASK | WRITERS_WAITING_MASK;
|
|
const COMPLETELY_UNLOCKED: u64 = 0;
|
|
|
|
/// Fast atomic lock state for single version
|
|
#[derive(Debug)]
|
|
pub struct AtomicLockState {
|
|
state: AtomicU64,
|
|
last_accessed: AtomicU64,
|
|
}
|
|
|
|
impl Default for AtomicLockState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl AtomicLockState {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
state: AtomicU64::new(0),
|
|
last_accessed: AtomicU64::new(
|
|
SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap_or(Duration::ZERO)
|
|
.as_secs(),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Check if fast path is available for given lock mode
|
|
#[inline(always)]
|
|
pub fn is_fast_path_available(&self, mode: LockMode) -> bool {
|
|
let state = self.state.load(Ordering::Relaxed); // Use Relaxed for better performance
|
|
match mode {
|
|
LockMode::Shared => {
|
|
// No writer and no waiting writers
|
|
(state & NO_WRITER_AND_NO_WAITING_WRITERS) == 0
|
|
}
|
|
LockMode::Exclusive => {
|
|
// Completely unlocked
|
|
state == COMPLETELY_UNLOCKED
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Try to acquire shared lock (fast path)
|
|
pub fn try_acquire_shared(&self) -> bool {
|
|
self.update_access_time();
|
|
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
|
|
// Fast path check - cannot acquire if there's a writer or writers waiting
|
|
if (current & NO_WRITER_AND_NO_WAITING_WRITERS) != 0 {
|
|
return false;
|
|
}
|
|
|
|
let readers = self.readers_count(current);
|
|
if readers == 0xFF {
|
|
// Updated limit to 255
|
|
return false; // Too many readers
|
|
}
|
|
|
|
let new_state = current + (1 << READERS_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Try to acquire exclusive lock (fast path)
|
|
pub fn try_acquire_exclusive(&self) -> bool {
|
|
self.update_access_time();
|
|
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
|
|
// Only ownership bits may block acquisition: no writer flag, no
|
|
// active readers. The waiting counters are preserved, not
|
|
// required to be zero — demanding a fully-zero word means a lock
|
|
// with registered waiters can be acquired by *no one*, including
|
|
// the waiters themselves (each sees the others' registration),
|
|
// so contended acquisition only succeeds in windows where every
|
|
// waiter happens to be unregistered. `try_acquire_shared` above
|
|
// already masks correctly; this mirrors it.
|
|
if (current & (WRITER_FLAG_MASK | READERS_MASK)) != 0 {
|
|
return false;
|
|
}
|
|
|
|
let new_state = current | WRITER_FLAG_MASK;
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Release shared lock
|
|
pub fn release_shared(&self) -> bool {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
let readers = self.readers_count(current);
|
|
|
|
if readers == 0 {
|
|
return false; // No shared lock to release
|
|
}
|
|
|
|
let new_state = current - (1 << READERS_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
self.update_access_time();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Release exclusive lock
|
|
pub fn release_exclusive(&self) -> bool {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
|
|
if (current & WRITER_FLAG_MASK) == 0 {
|
|
return false; // No exclusive lock to release
|
|
}
|
|
|
|
let new_state = current & !WRITER_FLAG_MASK;
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
self.update_access_time();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Increment waiting readers count
|
|
pub fn inc_readers_waiting(&self) -> bool {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
let waiting = self.readers_waiting(current);
|
|
|
|
if waiting == 0xFFFF {
|
|
return false; // Max waiting readers
|
|
}
|
|
|
|
let new_state = current + (1 << READERS_WAITING_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Decrement waiting readers count
|
|
pub fn dec_readers_waiting(&self) {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
let waiting = self.readers_waiting(current);
|
|
|
|
if waiting == 0 {
|
|
break; // No waiting readers
|
|
}
|
|
|
|
let new_state = current - (1 << READERS_WAITING_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Increment waiting writers count
|
|
pub fn inc_writers_waiting(&self) -> bool {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
let waiting = self.writers_waiting(current);
|
|
|
|
if waiting == 0xFFFF {
|
|
return false; // Max waiting writers
|
|
}
|
|
|
|
let new_state = current + (1 << WRITERS_WAITING_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Decrement waiting writers count
|
|
pub fn dec_writers_waiting(&self) {
|
|
loop {
|
|
let current = self.state.load(Ordering::Acquire);
|
|
let waiting = self.writers_waiting(current);
|
|
|
|
if waiting == 0 {
|
|
break; // No waiting writers
|
|
}
|
|
|
|
let new_state = current - (1 << WRITERS_WAITING_SHIFT);
|
|
|
|
if self
|
|
.state
|
|
.compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
|
|
.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if lock is completely free
|
|
pub fn is_free(&self) -> bool {
|
|
let state = self.state.load(Ordering::Acquire);
|
|
state == 0
|
|
}
|
|
|
|
/// Check if anyone is waiting
|
|
pub fn has_waiters(&self) -> bool {
|
|
let state = self.state.load(Ordering::Acquire);
|
|
self.readers_waiting(state) > 0 || self.writers_waiting(state) > 0
|
|
}
|
|
|
|
/// Get last access time
|
|
pub fn last_accessed(&self) -> u64 {
|
|
self.last_accessed.load(Ordering::Relaxed)
|
|
}
|
|
|
|
pub fn update_access_time(&self) {
|
|
let now = SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap_or(Duration::ZERO)
|
|
.as_secs();
|
|
self.last_accessed.store(now, Ordering::Relaxed);
|
|
}
|
|
|
|
fn readers_count(&self, state: u64) -> u8 {
|
|
((state & READERS_MASK) >> READERS_SHIFT) as u8
|
|
}
|
|
|
|
fn readers_waiting(&self, state: u64) -> u16 {
|
|
((state & READERS_WAITING_MASK) >> READERS_WAITING_SHIFT) as u16
|
|
}
|
|
|
|
fn writers_waiting(&self, state: u64) -> u16 {
|
|
((state & WRITERS_WAITING_MASK) >> WRITERS_WAITING_SHIFT) as u16
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn readers_waiting_count(&self) -> u16 {
|
|
let state = self.state.load(Ordering::Acquire);
|
|
self.readers_waiting(state)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn writers_waiting_count(&self) -> u16 {
|
|
let state = self.state.load(Ordering::Acquire);
|
|
self.writers_waiting(state)
|
|
}
|
|
}
|
|
|
|
/// Object lock state with version support - optimized memory layout
|
|
#[derive(Debug)]
|
|
#[repr(align(64))] // Align to cache line boundary
|
|
pub struct ObjectLockState {
|
|
// First cache line: Most frequently accessed data
|
|
/// Atomic state for fast operations
|
|
pub atomic_state: AtomicLockState,
|
|
|
|
// Second cache line: Notification mechanisms
|
|
/// Notification for readers (traditional)
|
|
pub read_notify: Notify,
|
|
/// Notification for writers (traditional)
|
|
pub write_notify: Notify,
|
|
/// Optimized notification system (optional)
|
|
pub optimized_notify: OptimizedNotify,
|
|
|
|
// Third cache line: Less frequently accessed data
|
|
/// Current owner of exclusive lock (if any)
|
|
pub current_owner: parking_lot::RwLock<Option<ExclusiveOwnerInfo>>,
|
|
/// Shared owners - optimized for small number of readers
|
|
pub shared_owners: parking_lot::RwLock<smallvec::SmallVec<[SharedOwnerEntry; 4]>>,
|
|
/// Lock priority for conflict resolution
|
|
pub priority: parking_lot::RwLock<LockPriority>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ExclusiveOwnerInfo {
|
|
pub owner: Arc<str>,
|
|
pub acquired_at: SystemTime,
|
|
pub lock_timeout: Duration,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SharedOwnerEntry {
|
|
pub owner: Arc<str>,
|
|
pub count: u32,
|
|
pub acquired_at: SystemTime,
|
|
pub lock_timeout: Duration,
|
|
}
|
|
|
|
impl Default for ObjectLockState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ObjectLockState {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
atomic_state: AtomicLockState::new(),
|
|
read_notify: Notify::new(),
|
|
write_notify: Notify::new(),
|
|
optimized_notify: OptimizedNotify::new(),
|
|
current_owner: parking_lot::RwLock::new(None),
|
|
shared_owners: parking_lot::RwLock::new(smallvec::SmallVec::new()),
|
|
priority: parking_lot::RwLock::new(LockPriority::Normal),
|
|
}
|
|
}
|
|
|
|
/// Try fast path shared lock acquisition
|
|
pub fn try_acquire_shared_fast(&self, owner: &Arc<str>, lock_timeout: Duration) -> bool {
|
|
if !self.atomic_state.try_acquire_shared() {
|
|
return false;
|
|
}
|
|
|
|
self.atomic_state.update_access_time();
|
|
let mut shared = self.shared_owners.write();
|
|
if let Some(entry) = shared.iter_mut().find(|entry| entry.owner.as_ref() == owner.as_ref()) {
|
|
entry.count = entry.count.saturating_add(1);
|
|
entry.acquired_at = SystemTime::now();
|
|
entry.lock_timeout = lock_timeout;
|
|
} else {
|
|
shared.push(SharedOwnerEntry {
|
|
owner: owner.clone(),
|
|
count: 1,
|
|
acquired_at: SystemTime::now(),
|
|
lock_timeout,
|
|
});
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Try fast path exclusive lock acquisition
|
|
pub fn try_acquire_exclusive_fast(&self, owner: &Arc<str>, lock_timeout: Duration) -> bool {
|
|
if !self.atomic_state.try_acquire_exclusive() {
|
|
return false;
|
|
}
|
|
|
|
self.atomic_state.update_access_time();
|
|
let mut current = self.current_owner.write();
|
|
*current = Some(ExclusiveOwnerInfo {
|
|
owner: owner.clone(),
|
|
acquired_at: SystemTime::now(),
|
|
lock_timeout,
|
|
});
|
|
true
|
|
}
|
|
|
|
/// Release shared lock
|
|
pub fn release_shared(&self, owner: &Arc<str>) -> bool {
|
|
let mut shared = self.shared_owners.write();
|
|
if let Some(pos) = shared.iter().position(|entry| entry.owner.as_ref() == owner.as_ref()) {
|
|
let original_entry = shared[pos].clone();
|
|
let removed_entry = if shared[pos].count > 1 {
|
|
shared[pos].count -= 1;
|
|
None
|
|
} else {
|
|
Some(shared.remove(pos))
|
|
};
|
|
if self.atomic_state.release_shared() {
|
|
if shared.is_empty() {
|
|
drop(shared);
|
|
self.optimized_notify.notify_writer();
|
|
}
|
|
true
|
|
} else {
|
|
tracing::warn!(
|
|
"Atomic state inconsistency during shared lock release: owner={}, remaining_entries={}",
|
|
owner,
|
|
shared.len()
|
|
);
|
|
// Re-add owner entry to maintain consistency when release failed
|
|
match removed_entry {
|
|
Some(entry) => {
|
|
shared.push(entry);
|
|
}
|
|
None => {
|
|
if let Some(existing) = shared.iter_mut().find(|existing| existing.owner.as_ref() == owner.as_ref()) {
|
|
existing.count = existing.count.saturating_add(1);
|
|
} else {
|
|
shared.push(original_entry);
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
} else {
|
|
tracing::debug!(
|
|
"Shared lock release failed - owner not found: owner={}, current_entries={:?}",
|
|
owner,
|
|
shared.iter().map(|s| s.owner.as_ref()).collect::<Vec<_>>()
|
|
);
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Release exclusive lock
|
|
pub fn release_exclusive(&self, owner: &Arc<str>) -> bool {
|
|
let mut current = self.current_owner.write();
|
|
if current.as_ref().is_some_and(|info| info.owner.as_ref() == owner.as_ref()) {
|
|
if self.atomic_state.release_exclusive() {
|
|
*current = None;
|
|
drop(current);
|
|
// Notify waiters using optimized system - prefer writers over readers
|
|
if self
|
|
.atomic_state
|
|
.writers_waiting(self.atomic_state.state.load(Ordering::Acquire))
|
|
> 0
|
|
{
|
|
self.optimized_notify.notify_writer();
|
|
} else {
|
|
self.optimized_notify.notify_readers();
|
|
}
|
|
true
|
|
} else {
|
|
// Atomic state inconsistency - current owner matches but atomic release failed
|
|
tracing::warn!(
|
|
"Atomic state inconsistency during exclusive lock release: owner={}, atomic_state={:b}",
|
|
owner,
|
|
self.atomic_state.state.load(Ordering::Acquire)
|
|
);
|
|
false
|
|
}
|
|
} else {
|
|
// Owner mismatch
|
|
tracing::debug!(
|
|
"Exclusive lock release failed - owner mismatch: expected_owner={}, actual_owner={:?}",
|
|
owner,
|
|
current.as_ref().map(|s| s.owner.as_ref())
|
|
);
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Check if object is locked
|
|
pub fn is_locked(&self) -> bool {
|
|
!self.atomic_state.is_free()
|
|
}
|
|
|
|
/// Get current lock mode
|
|
pub fn current_mode(&self) -> Option<LockMode> {
|
|
let state = self.atomic_state.state.load(Ordering::Acquire);
|
|
if (state & WRITER_FLAG_MASK) != 0 {
|
|
Some(LockMode::Exclusive)
|
|
} else if self.atomic_state.readers_count(state) > 0 {
|
|
Some(LockMode::Shared)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_atomic_lock_state() {
|
|
let state = AtomicLockState::new();
|
|
|
|
// Test shared lock
|
|
assert!(state.try_acquire_shared());
|
|
assert!(state.try_acquire_shared());
|
|
assert!(!state.try_acquire_exclusive());
|
|
|
|
assert!(state.release_shared());
|
|
assert!(state.release_shared());
|
|
assert!(!state.release_shared());
|
|
|
|
// Test exclusive lock
|
|
assert!(state.try_acquire_exclusive());
|
|
assert!(!state.try_acquire_shared());
|
|
assert!(!state.try_acquire_exclusive());
|
|
|
|
assert!(state.release_exclusive());
|
|
assert!(!state.release_exclusive());
|
|
}
|
|
|
|
// Regression for the waiter-preserving exclusive CAS.
|
|
//
|
|
// The acquisition CAS used to demand a fully-zero state word, which
|
|
// includes the readers_waiting/writers_waiting counters. A free lock with
|
|
// registered waiters was then acquirable by *no one* — including the
|
|
// waiters themselves, each blocked by the others' registration — so
|
|
// contended acquisition only succeeded in windows where every waiter
|
|
// happened to be unregistered. Reverting to `expected = 0` must fail here.
|
|
#[test]
|
|
fn exclusive_acquisition_ignores_registered_waiters() {
|
|
let state = AtomicLockState::new();
|
|
|
|
// Waiters register while the lock is held, then the holder releases.
|
|
assert!(state.try_acquire_exclusive());
|
|
assert!(state.inc_writers_waiting());
|
|
assert!(state.inc_readers_waiting());
|
|
assert!(state.release_exclusive());
|
|
|
|
// The lock is now free — only the waiting counters are set.
|
|
assert!(
|
|
state.try_acquire_exclusive(),
|
|
"registered waiters must not block acquisition of a free lock"
|
|
);
|
|
// ...and the CAS must preserve those counters, not clobber them.
|
|
assert_eq!(state.writers_waiting_count(), 1);
|
|
assert_eq!(state.readers_waiting_count(), 1);
|
|
|
|
assert!(state.release_exclusive());
|
|
state.dec_writers_waiting();
|
|
|
|
// Ownership bits still block, registered waiters or not.
|
|
assert!(state.try_acquire_shared());
|
|
assert!(!state.try_acquire_exclusive(), "an active reader must still block");
|
|
assert!(state.release_shared());
|
|
|
|
state.dec_readers_waiting();
|
|
assert!(state.is_free());
|
|
}
|
|
|
|
#[test]
|
|
fn test_object_lock_state() {
|
|
let state = ObjectLockState::new();
|
|
let owner1 = Arc::from("owner1");
|
|
let owner2 = Arc::from("owner2");
|
|
|
|
// Test shared locks
|
|
let timeout = Duration::from_secs(30);
|
|
|
|
assert!(state.try_acquire_shared_fast(&owner1, timeout));
|
|
assert!(state.try_acquire_shared_fast(&owner2, timeout));
|
|
assert!(!state.try_acquire_exclusive_fast(&owner1, timeout));
|
|
|
|
assert!(state.release_shared(&owner1));
|
|
assert!(state.release_shared(&owner2));
|
|
|
|
// Test exclusive lock
|
|
assert!(state.try_acquire_exclusive_fast(&owner1, timeout));
|
|
assert!(!state.try_acquire_shared_fast(&owner2, timeout));
|
|
assert!(state.release_exclusive(&owner1));
|
|
}
|
|
}
|