use crate::sync::{AtomicUsize, Condvar, Mutex, Ordering};
#[doc(hidden)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Point {
ParkRaised,
ParkChecked,
WakeBegin,
}
#[doc(hidden)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mutation {
None,
SkipRecheck,
NoLockHandoff,
}
#[inline]
fn hook(_point: Point) {
#[cfg(feature = "test-hooks")]
crate::test_hooks::fire(_point);
}
#[inline]
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
fn skip_recheck() -> bool {
#[cfg(feature = "test-hooks")]
{
crate::test_hooks::mutation() == Mutation::SkipRecheck
}
#[cfg(not(feature = "test-hooks"))]
{
false
}
}
#[inline]
fn no_lock_handoff() -> bool {
#[cfg(feature = "test-hooks")]
{
crate::test_hooks::mutation() == Mutation::NoLockHandoff
}
#[cfg(not(feature = "test-hooks"))]
{
false
}
}
#[doc(hidden)]
pub struct Parker {
lock: Mutex<()>,
cv: Condvar,
parked: AtomicUsize,
}
impl Default for Parker {
fn default() -> Self {
Self::new()
}
}
impl Parker {
pub fn new() -> Self {
let p = Parker {
lock: Mutex::new(()),
cv: Condvar::new(),
parked: AtomicUsize::new(0),
};
drop(p.lock.lock().unwrap_or_else(|e| e.into_inner()));
p.cv.notify_all();
p
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub fn park_while(&self, still_waiting: impl Fn() -> bool) {
self.parked.fetch_add(1, Ordering::AcqRel);
hook(Point::ParkRaised);
let mut guard = self.lock.lock().unwrap_or_else(|e| e.into_inner());
let mut first = true;
while (first && skip_recheck()) || still_waiting() {
first = false;
hook(Point::ParkChecked);
guard = self.cv.wait(guard).unwrap_or_else(|e| e.into_inner());
}
drop(guard);
self.parked.fetch_sub(1, Ordering::AcqRel);
}
pub fn wake(&self) {
hook(Point::WakeBegin);
if self.parked.fetch_add(0, Ordering::AcqRel) != 0 {
if !no_lock_handoff() {
drop(self.lock.lock().unwrap_or_else(|e| e.into_inner()));
}
self.cv.notify_all();
}
}
}