use std::ops::{Deref, DerefMut};
use crate::loom_exports::sync::atomic::{AtomicUsize, Ordering};
use crate::loom_exports::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
#[derive(Clone)]
pub(crate) struct CachedRwLock<T: Clone> {
value: T,
epoch: usize,
shared: Arc<Shared<T>>,
}
impl<T: Clone> CachedRwLock<T> {
pub(crate) fn new(t: T) -> Self {
let shared = t.clone();
Self {
value: t,
epoch: 0,
shared: Arc::new(Shared {
value: Mutex::new(shared),
epoch: AtomicUsize::new(0),
}),
}
}
pub(crate) fn read_unsync(&self) -> &T {
&self.value
}
#[allow(dead_code)]
pub(crate) fn read(&mut self) -> LockResult<&T> {
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
match self.shared.value.lock() {
LockResult::Ok(shared) => {
self.value = shared.clone();
self.epoch = self.shared.epoch.load(Ordering::Relaxed)
}
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&self.value)),
}
}
LockResult::Ok(&self.value)
}
#[allow(dead_code)]
pub(crate) fn write_scratchpad_unsync(&mut self) -> &mut T {
&mut self.value
}
pub(crate) fn write_scratchpad(&mut self) -> LockResult<&mut T> {
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
match self.shared.value.lock() {
LockResult::Ok(shared) => {
self.value = shared.clone();
self.epoch = self.shared.epoch.load(Ordering::Relaxed)
}
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&mut self.value)),
}
}
LockResult::Ok(&mut self.value)
}
pub(crate) fn write(&mut self) -> LockResult<CachedRwLockWriteGuard<'_, T>> {
let guard = self.shared.value.lock();
let epoch = self.shared.epoch.load(Ordering::Relaxed) + 1;
self.shared.epoch.store(epoch, Ordering::Relaxed);
match guard {
LockResult::Ok(shared) => LockResult::Ok(CachedRwLockWriteGuard { guard: shared }),
LockResult::Err(poison) => LockResult::Err(PoisonError::new(CachedRwLockWriteGuard {
guard: poison.into_inner(),
})),
}
}
}
struct Shared<T> {
epoch: AtomicUsize,
value: Mutex<T>,
}
pub(crate) struct CachedRwLockWriteGuard<'a, T: Clone> {
guard: MutexGuard<'a, T>,
}
impl<T: Clone> Deref for CachedRwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.guard
}
}
impl<T: Clone> DerefMut for CachedRwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.guard
}
}
#[cfg(all(test, nexosim_loom))]
mod tests {
use super::*;
use loom::model::Builder;
use loom::thread;
#[test]
fn loom_cached_rw_lock_write() {
const DEFAULT_PREEMPTION_BOUND: usize = 3;
const ITERATIONS_NUMBER: usize = 5;
let mut builder = Builder::new();
if builder.preemption_bound.is_none() {
builder.preemption_bound = Some(DEFAULT_PREEMPTION_BOUND);
}
builder.check(move || {
let mut writer0: CachedRwLock<usize> = CachedRwLock::new(0);
let mut writer1 = writer0.clone();
let mut reader = writer0.clone();
let th_w = thread::spawn(move || {
for _ in 0..ITERATIONS_NUMBER {
let mut guard = writer0.write().unwrap();
*guard = *guard + 1;
}
});
let th_r = thread::spawn(move || {
let mut value = 0;
let mut prev_value;
for _ in 0..ITERATIONS_NUMBER {
prev_value = value;
value = *reader.write_scratchpad().unwrap();
assert!(
prev_value <= value,
"Previous value = {}, value = {}",
prev_value,
value
);
assert_eq!(value, reader.epoch);
}
});
for _ in 0..ITERATIONS_NUMBER {
let mut guard = writer1.write().unwrap();
*guard = *guard + 1;
}
th_w.join().unwrap();
th_r.join().unwrap();
});
}
}