blob: 938b7071b88a7d787a4e0ad2cadbe8443fc48e09 [file] [log] [blame]
#![forbid(unsafe_op_in_unsafe_fn)]
use crate::pin::Pin;
use crate::ptr;
use crate::sync::atomic::Ordering::Relaxed;
use crate::sync::atomic::{Atomic, AtomicUsize};
use crate::sys::pal::sync as pal;
use crate::sys::sync::{Mutex, OnceBox};
use crate::time::{Duration, Instant};
pub struct Condvar {
cvar: OnceBox<pal::Condvar>,
mutex: Atomic<usize>,
}
impl Condvar {
pub const fn new() -> Condvar {
Condvar { cvar: OnceBox::new(), mutex: AtomicUsize::new(0) }
}
#[inline]
fn get(&self) -> Pin<&pal::Condvar> {
self.cvar.get_or_init(|| {
let mut cvar = Box::pin(pal::Condvar::new());
// SAFETY: we only call `init` once per `pal::Condvar`, namely here.
unsafe { cvar.as_mut().init() };
cvar
})
}
#[inline]
fn verify(&self, mutex: Pin<&pal::Mutex>) {
let addr = ptr::from_ref::<pal::Mutex>(&mutex).addr();
// Relaxed is okay here because we never read through `self.mutex`, and only use it to
// compare addresses.
match self.mutex.compare_exchange(0, addr, Relaxed, Relaxed) {
Ok(_) => {} // Stored the address
Err(n) if n == addr => {} // Lost a race to store the same address
_ => panic!("attempted to use a condition variable with two mutexes"),
}
}
#[inline]
pub fn notify_one(&self) {
// SAFETY: we called `init` above.
unsafe { self.get().notify_one() }
}
#[inline]
pub fn notify_all(&self) {
// SAFETY: we called `init` above.
unsafe { self.get().notify_all() }
}
#[inline]
pub unsafe fn wait(&self, mutex: &Mutex) {
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
// must have been initialized already.
let mutex = unsafe { mutex.pal.get_unchecked() };
self.verify(mutex);
// SAFETY: we called `init` above, we verified that this condition
// variable is only used with `mutex` and the caller guarantees that
// `mutex` is locked by the current thread.
unsafe { self.get().wait(mutex) }
}
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
// must have been initialized already.
let mutex = unsafe { mutex.pal.get_unchecked() };
self.verify(mutex);
if pal::Condvar::PRECISE_TIMEOUT {
// SAFETY: we called `init` above, we verified that this condition
// variable is only used with `mutex` and the caller guarantees that
// `mutex` is locked by the current thread.
unsafe { self.get().wait_timeout(mutex, dur) }
} else {
// Timeout reports are not reliable, so do the check ourselves.
let now = Instant::now();
// SAFETY: we called `init` above, we verified that this condition
// variable is only used with `mutex` and the caller guarantees that
// `mutex` is locked by the current thread.
let woken = unsafe { self.get().wait_timeout(mutex, dur) };
woken || now.elapsed() < dur
}
}
}