#[cfg(loom)]
pub(crate) use loom::sync::Arc;
#[cfg(not(loom))]
pub(crate) use std::sync::Arc;
#[cfg(loom)]
pub(crate) use loom::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering};
#[cfg(not(loom))]
pub(crate) use std::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering};
#[cfg(all(loom, debug_assertions))]
pub(crate) use loom::sync::atomic::AtomicBool;
#[cfg(all(not(loom), debug_assertions))]
pub(crate) use std::sync::atomic::AtomicBool;
#[cfg(loom)]
use loom::sync as imp;
#[cfg(not(loom))]
use std::sync as imp;
pub(crate) use imp::{Condvar, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
use std::sync::{PoisonError, TryLockError};
#[derive(Default)]
pub(crate) struct Mutex<T>(imp::Mutex<T>);
impl<T> Mutex<T> {
#[cfg(not(loom))]
pub(crate) const fn new(value: T) -> Self {
Self(imp::Mutex::new(value))
}
#[cfg(loom)]
pub(crate) fn new(value: T) -> Self {
Self(imp::Mutex::new(value))
}
#[allow(dead_code)]
pub(crate) fn into_inner(self) -> T {
self.0.into_inner().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T> Mutex<T> {
pub(crate) fn lock(&self) -> MutexGuard<'_, T> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
match self.0.try_lock() {
Ok(guard) => Some(guard),
Err(TryLockError::Poisoned(poisoned)) => Some(poisoned.into_inner()),
Err(TryLockError::WouldBlock) => None,
}
}
#[allow(dead_code)]
pub(crate) fn get_mut(&mut self) -> &mut T {
self.0.get_mut().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Default)]
pub(crate) struct RwLock<T>(imp::RwLock<T>);
impl<T> RwLock<T> {
#[cfg(not(loom))]
pub(crate) const fn new(value: T) -> Self {
Self(imp::RwLock::new(value))
}
#[cfg(loom)]
pub(crate) fn new(value: T) -> Self {
Self(imp::RwLock::new(value))
}
}
impl<T> RwLock<T> {
pub(crate) fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.read().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.write().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for RwLock<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Default)]
struct GateState {
readers: usize,
writing: bool,
writers_waiting: usize,
}
pub(crate) struct Gate {
state: Mutex<GateState>,
cv: Condvar,
}
impl Gate {
pub(crate) fn new() -> Self {
Self {
state: Mutex::new(GateState::default()),
cv: Condvar::new(),
}
}
pub(crate) fn read(&self) -> GateReadGuard<'_> {
let mut state = self.state.lock();
while state.writing || state.writers_waiting > 0 {
state = self.cv.wait(state).unwrap_or_else(PoisonError::into_inner);
}
state.readers += 1;
drop(state);
GateReadGuard { gate: self }
}
pub(crate) fn write(&self) -> GateWriteGuard<'_> {
self.acquire_write();
GateWriteGuard { gate: self }
}
pub(crate) fn write_owned(gate: &std::sync::Arc<Self>) -> OwnedGateWriteGuard {
gate.acquire_write();
OwnedGateWriteGuard {
gate: std::sync::Arc::clone(gate),
}
}
fn acquire_write(&self) {
let mut state = self.state.lock();
state.writers_waiting += 1;
while state.writing || state.readers > 0 {
state = self.cv.wait(state).unwrap_or_else(PoisonError::into_inner);
}
state.writers_waiting -= 1;
state.writing = true;
}
fn release_read(&self) {
let mut state = self.state.lock();
state.readers -= 1;
let idle = state.readers == 0;
drop(state);
if idle {
self.cv.notify_all();
}
}
fn release_write(&self) {
let mut state = self.state.lock();
state.writing = false;
drop(state);
self.cv.notify_all();
}
}
impl Default for Gate {
fn default() -> Self {
Self::new()
}
}
pub(crate) struct GateReadGuard<'a> {
gate: &'a Gate,
}
impl Drop for GateReadGuard<'_> {
fn drop(&mut self) {
self.gate.release_read();
}
}
pub(crate) struct GateWriteGuard<'a> {
gate: &'a Gate,
}
impl Drop for GateWriteGuard<'_> {
fn drop(&mut self) {
self.gate.release_write();
}
}
pub(crate) struct OwnedGateWriteGuard {
gate: std::sync::Arc<Gate>,
}
impl Drop for OwnedGateWriteGuard {
fn drop(&mut self) {
self.gate.release_write();
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
#[test]
fn a_lock_whose_holder_panicked_still_hands_back_its_contents() {
let mutex = Arc::new(Mutex::new(vec![1u32, 2, 3]));
let poisoner = Arc::clone(&mutex);
let panicked = std::thread::spawn(move || {
let mut guard = poisoner.lock();
guard.push(4);
panic!("the holder dies mid-update");
})
.join();
assert!(panicked.is_err(), "the probe needs the thread to panic");
assert_eq!(
*mutex.lock(),
vec![1, 2, 3, 4],
"poisoning must not hide what the panicking thread had already written",
);
assert!(mutex.try_lock().is_some(), "try_lock must recover too");
}
#[test]
fn an_rwlock_whose_writer_panicked_still_reads() {
let lock = Arc::new(RwLock::new(7u32));
let poisoner = Arc::clone(&lock);
let _ = std::thread::spawn(move || {
let mut guard = poisoner.write();
*guard = 9;
panic!("the writer dies mid-update");
})
.join();
assert_eq!(*lock.read(), 9);
assert_eq!(*lock.write(), 9);
}
#[test]
fn a_gate_admits_many_readers_but_only_one_writer() {
let gate = Arc::new(Gate::new());
let live = Arc::new(StdAtomicUsize::new(0));
let peak = Arc::new(StdAtomicUsize::new(0));
let exclusive_overlaps = Arc::new(StdAtomicUsize::new(0));
std::thread::scope(|scope| {
for i in 0..8 {
let (gate, live, peak, overlaps) = (
Arc::clone(&gate),
Arc::clone(&live),
Arc::clone(&peak),
Arc::clone(&exclusive_overlaps),
);
scope.spawn(move || {
for _ in 0..200 {
if i % 4 == 0 {
let _w = gate.write();
if live.load(StdOrdering::SeqCst) != 0 {
overlaps.fetch_add(1, StdOrdering::SeqCst);
}
std::thread::yield_now();
if live.load(StdOrdering::SeqCst) != 0 {
overlaps.fetch_add(1, StdOrdering::SeqCst);
}
} else {
let _r = gate.read();
let now = live.fetch_add(1, StdOrdering::SeqCst) + 1;
peak.fetch_max(now, StdOrdering::SeqCst);
std::thread::yield_now();
live.fetch_sub(1, StdOrdering::SeqCst);
}
}
});
}
});
assert_eq!(
exclusive_overlaps.load(StdOrdering::SeqCst),
0,
"a writer saw a reader inside the gate",
);
assert!(
peak.load(StdOrdering::SeqCst) > 1,
"readers never overlapped, so the gate is serializing them like a mutex",
);
}
#[test]
fn an_owned_write_guard_outlives_the_call_that_took_it() {
let gate = Arc::new(Gate::new());
let guard = held_by_a_returned_value(&gate);
assert!(
std::thread::scope(|scope| {
let gate = Arc::clone(&gate);
let probe = scope.spawn(move || gate.state.try_lock().is_some());
probe.join().expect("probe")
}),
"the gate's own mutex must not be held between operations",
);
drop(guard);
drop(gate.write());
}
fn held_by_a_returned_value(gate: &Arc<Gate>) -> OwnedGateWriteGuard {
Gate::write_owned(gate)
}
#[test]
fn a_waiting_writer_blocks_new_readers() {
let gate = Arc::new(Gate::new());
let held = gate.read();
let entered = Arc::new(StdAtomicUsize::new(0));
std::thread::scope(|scope| {
let (writer_gate, writer_entered) = (Arc::clone(&gate), Arc::clone(&entered));
let writer = scope.spawn(move || {
let _w = writer_gate.write();
writer_entered.fetch_add(1, StdOrdering::SeqCst);
});
while gate.state.lock().writers_waiting == 0 {
std::thread::yield_now();
}
assert!(
gate.state.lock().readers == 1,
"the probe needs exactly the one reader it took",
);
drop(held);
writer.join().expect("writer");
});
assert_eq!(entered.load(StdOrdering::SeqCst), 1);
}
}