use crate::core::detector;
use crate::core::locks::{NEXT_LOCK_ID, contention::ContentionState};
use crate::core::types::{LockId, ThreadId, get_current_thread_id};
#[cfg(feature = "logging-and-visualization")]
use crate::core::{Events, logger};
use parking_lot::{
RwLock as ParkingLotRwLock, RwLockReadGuard as ParkingLotReadGuard,
RwLockWriteGuard as ParkingLotWriteGuard,
};
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct RwLock<T> {
id: LockId,
inner: ParkingLotRwLock<T>,
creator_thread_id: ThreadId,
writer_owner: AtomicUsize,
contention: ContentionState,
}
pub struct RwLockReadGuard<'a, T> {
thread_id: ThreadId,
lock_id: LockId,
guard: ParkingLotReadGuard<'a, T>,
}
pub struct RwLockWriteGuard<'a, T> {
thread_id: ThreadId,
lock_id: LockId,
guard: ParkingLotWriteGuard<'a, T>,
owner_atomic: &'a AtomicUsize,
tracked_globally: bool,
contention: &'a ContentionState,
}
impl<T> RwLock<T> {
pub fn new(value: T) -> Self {
let id = NEXT_LOCK_ID.fetch_add(1, Ordering::SeqCst);
let creator_thread_id = get_current_thread_id();
detector::rwlock::create_rwlock(id, Some(creator_thread_id));
RwLock {
id,
inner: ParkingLotRwLock::new(value),
creator_thread_id,
writer_owner: AtomicUsize::new(0),
contention: ContentionState::new(),
}
}
pub fn id(&self) -> LockId {
self.id
}
pub fn creator_thread_id(&self) -> ThreadId {
self.creator_thread_id
}
pub fn read(&self) -> RwLockReadGuard<'_, T> {
let thread_id = get_current_thread_id();
if let Some(guard) =
crate::core::detector::rwlock::try_read(thread_id, self.id, || self.inner.try_read())
{
return RwLockReadGuard {
thread_id,
lock_id: self.id,
guard,
};
}
let slow_waiter = self.contention.register();
let (rechecked_guard, deadlock_info) = detector::rwlock::acquire_read_slow_with_recheck(
thread_id,
self.id,
|| self.inner.try_read(),
|| {
let writer = self.writer_owner.load(Ordering::Acquire);
(writer != 0).then_some(writer as ThreadId)
},
);
if let Some(info) = deadlock_info {
detector::deadlock_handling::process_deadlock(info);
}
let guard = if let Some(guard) = rechecked_guard {
guard
} else {
let guard = self.inner.read();
detector::rwlock::complete_read(thread_id, self.id);
guard
};
drop(slow_waiter);
RwLockReadGuard {
thread_id,
lock_id: self.id,
guard,
}
}
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
let thread_id = get_current_thread_id();
let tid_usize = thread_id as usize;
#[cfg(not(feature = "stress-test"))]
if let Some(guard) = self.inner.try_write() {
self.writer_owner.store(tid_usize, Ordering::Release);
let tracked_globally =
cfg!(feature = "lock-order-graph") || self.contention.has_waiters();
#[cfg(feature = "logging-and-visualization")]
{
if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
logger::log_interaction_event(thread_id, self.id, Events::RwWriteAttempt);
}
}
if tracked_globally {
detector::rwlock::complete_write(thread_id, self.id);
}
#[cfg(feature = "logging-and-visualization")]
{
if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
logger::log_interaction_event(thread_id, self.id, Events::RwWriteAcquired);
}
}
return RwLockWriteGuard {
thread_id,
lock_id: self.id,
guard,
owner_atomic: &self.writer_owner,
tracked_globally,
contention: &self.contention,
};
}
let slow_waiter = self.contention.register();
let (rechecked_guard, deadlock_info) = detector::rwlock::acquire_write_slow_with_recheck(
thread_id,
self.id,
|| self.inner.try_write(),
|| {
let writer = self.writer_owner.load(Ordering::Acquire);
(writer != 0).then_some(writer as ThreadId)
},
);
if let Some(info) = deadlock_info {
detector::deadlock_handling::process_deadlock(info);
}
if let Some(guard) = rechecked_guard {
self.writer_owner.store(tid_usize, Ordering::Release);
drop(slow_waiter);
return RwLockWriteGuard {
thread_id,
lock_id: self.id,
guard,
owner_atomic: &self.writer_owner,
tracked_globally: true,
contention: &self.contention,
};
}
let guard = self.inner.write();
self.writer_owner.store(tid_usize, Ordering::Release);
detector::rwlock::complete_write(thread_id, self.id);
drop(slow_waiter);
RwLockWriteGuard {
thread_id,
lock_id: self.id,
guard,
owner_atomic: &self.writer_owner,
tracked_globally: true,
contention: &self.contention,
}
}
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
let thread_id = get_current_thread_id();
let guard = detector::rwlock::try_read(thread_id, self.id, || self.inner.try_read());
guard.map(|g| RwLockReadGuard {
thread_id,
lock_id: self.id,
guard: g,
})
}
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
let thread_id = get_current_thread_id();
if let Some(guard) = self.inner.try_write() {
self.writer_owner
.store(thread_id as usize, Ordering::Release);
let tracked_globally =
cfg!(feature = "lock-order-graph") || self.contention.has_waiters();
#[cfg(feature = "logging-and-visualization")]
{
if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
logger::log_interaction_event(thread_id, self.id, Events::RwWriteAttempt);
}
}
if tracked_globally {
detector::rwlock::complete_write(thread_id, self.id);
}
#[cfg(feature = "logging-and-visualization")]
{
if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
logger::log_interaction_event(thread_id, self.id, Events::RwWriteAcquired);
}
}
Some(RwLockWriteGuard {
thread_id,
lock_id: self.id,
guard,
owner_atomic: &self.writer_owner,
tracked_globally,
contention: &self.contention,
})
} else {
None
}
}
pub fn into_inner(self) -> T
where
T: Sized,
{
detector::rwlock::destroy_rwlock(self.id);
let rwlock = std::mem::ManuallyDrop::new(self);
unsafe { std::ptr::read(&rwlock.inner) }.into_inner()
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut()
}
}
impl<T> Drop for RwLock<T> {
fn drop(&mut self) {
detector::rwlock::destroy_rwlock(self.id);
}
}
impl<'a, T> Deref for RwLockReadGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guard.deref()
}
}
impl<'a, T> Drop for RwLockReadGuard<'a, T> {
fn drop(&mut self) {
detector::rwlock::release_read(self.thread_id, self.lock_id);
}
}
impl<'a, T> Deref for RwLockWriteGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guard.deref()
}
}
impl<'a, T> DerefMut for RwLockWriteGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.guard.deref_mut()
}
}
impl<'a, T> Drop for RwLockWriteGuard<'a, T> {
fn drop(&mut self) {
self.owner_atomic.store(0, Ordering::Release);
if self.tracked_globally || self.contention.has_waiters() {
detector::rwlock::release_write(self.thread_id, self.lock_id);
} else {
#[cfg(feature = "logging-and-visualization")]
if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
logger::log_interaction_event(
self.thread_id,
self.lock_id,
Events::RwWriteReleased,
);
}
}
}
}
impl<T: Default> Default for RwLock<T> {
fn default() -> RwLock<T> {
RwLock::new(Default::default())
}
}
impl<T> From<T> for RwLock<T> {
fn from(t: T) -> Self {
RwLock::new(t)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, mpsc};
use std::time::{Duration, Instant};
#[test]
fn blocking_reader_wait_is_visible_while_fast_writer_holds_lock() {
let lock = Arc::new(RwLock::new(()));
let writer = lock.write();
let reader_lock = Arc::clone(&lock);
let (acquired_tx, acquired_rx) = mpsc::channel();
let reader = std::thread::spawn(move || {
let _guard = reader_lock.read();
acquired_tx.send(()).unwrap();
});
let deadline = Instant::now() + Duration::from_secs(1);
while !lock.contention.has_waiters() && Instant::now() < deadline {
std::thread::yield_now();
}
assert!(lock.contention.has_waiters());
drop(writer);
acquired_rx.recv_timeout(Duration::from_secs(1)).unwrap();
reader.join().unwrap();
assert!(!lock.contention.has_waiters());
}
}