use std::hint::spin_loop;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
#[derive(Debug)]
pub struct ReadWriteCASLock {
write_lock: AtomicBool,
read_lock: AtomicI32,
}
impl Default for ReadWriteCASLock {
fn default() -> Self {
Self::new()
}
}
impl ReadWriteCASLock {
pub fn new() -> Self {
Self {
write_lock: AtomicBool::new(true),
read_lock: AtomicI32::new(0),
}
}
pub fn acquire_write_lock(&self) {
while self
.write_lock
.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
spin_loop();
}
while self.read_lock.load(Ordering::Acquire) != 0 {
spin_loop();
}
}
pub fn release_write_lock(&self) {
let _ = self
.write_lock
.compare_exchange(false, true, Ordering::Release, Ordering::Relaxed);
}
pub fn acquire_read_lock(&self) {
while !self.write_lock.load(Ordering::Acquire) {
spin_loop();
}
self.read_lock.fetch_add(1, Ordering::AcqRel);
}
pub fn release_read_lock(&self) {
self.read_lock.fetch_sub(1, Ordering::AcqRel);
}
pub fn get_write_lock(&self) -> bool {
self.write_lock.load(Ordering::Acquire) && self.read_lock.load(Ordering::Acquire) == 0
}
pub fn get_read_lock(&self) -> bool {
self.write_lock.load(Ordering::Acquire)
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::Barrier;
use std::thread;
use std::time::Duration;
use super::*;
#[test]
fn read_write_cas_lock_state_matches_java_accessors() {
let lock = ReadWriteCASLock::new();
assert!(lock.get_write_lock());
assert!(lock.get_read_lock());
lock.acquire_read_lock();
assert!(!lock.get_write_lock());
assert!(lock.get_read_lock());
lock.release_read_lock();
assert!(lock.get_write_lock());
assert!(lock.get_read_lock());
lock.acquire_write_lock();
assert!(!lock.get_write_lock());
assert!(!lock.get_read_lock());
lock.release_write_lock();
assert!(lock.get_write_lock());
assert!(lock.get_read_lock());
}
#[test]
fn write_lock_waits_for_active_readers_like_java() {
let lock = Arc::new(ReadWriteCASLock::new());
lock.acquire_read_lock();
let writer_started = Arc::new(Barrier::new(2));
let writer_acquired = Arc::new(AtomicBool::new(false));
let writer_lock = Arc::clone(&lock);
let writer_started_inner = Arc::clone(&writer_started);
let writer_acquired_inner = Arc::clone(&writer_acquired);
let handle = thread::spawn(move || {
writer_started_inner.wait();
writer_lock.acquire_write_lock();
writer_acquired_inner.store(true, Ordering::Release);
writer_lock.release_write_lock();
});
writer_started.wait();
thread::sleep(Duration::from_millis(20));
assert!(!writer_acquired.load(Ordering::Acquire));
lock.release_read_lock();
handle.join().expect("writer thread panicked");
assert!(writer_acquired.load(Ordering::Acquire));
assert!(lock.get_write_lock());
}
}