use crate::sync::{AtomicBool, 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();
}
}
}
pub(crate) const THROTTLED_POLLS: u32 = 100;
const YIELD_POLLS: u32 = if cfg!(loom) { 0 } else { 16 };
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Step {
Poll,
Sleep,
}
#[doc(hidden)]
pub struct Waiter {
budget: u32,
polls: u32,
yields: u32,
slept: bool,
}
impl Waiter {
pub fn new(budget: u32) -> Self {
Waiter {
budget,
polls: 0,
yields: 0,
slept: false,
}
}
pub fn slept(&self) -> bool {
self.slept
}
#[inline]
pub fn step(&mut self) -> Step {
if self.polls < self.budget {
self.polls += 1;
std::hint::spin_loop();
return Step::Poll;
}
if self.yields < YIELD_POLLS {
self.yields += 1;
std::thread::yield_now();
return Step::Poll;
}
self.slept = true;
Step::Sleep
}
}
#[repr(align(128))]
struct Pad<T>(T);
#[doc(hidden)]
pub struct Completion {
remaining: Pad<AtomicUsize>,
}
impl Completion {
pub fn new(n: usize) -> Self {
Completion {
remaining: Pad(AtomicUsize::new(n)),
}
}
pub fn is_done(&self) -> bool {
self.remaining.0.load(Ordering::Acquire) == 0
}
pub unsafe fn finish(this: *const Completion, sync: Option<&Parker>) {
if (*this).remaining.0.fetch_sub(1, Ordering::SeqCst) == 1 {
if let Some(sync) = sync {
sync.wake();
}
}
}
pub fn wait(&self, sync: Option<&Parker>, waiter: &mut Waiter) {
while !self.is_done() {
if waiter.step() == Step::Sleep {
match sync {
Some(sync) => sync.park_while(|| self.remaining.0.load(Ordering::SeqCst) != 0),
None => std::hint::spin_loop(),
}
}
}
}
}
#[doc(hidden)]
pub struct Barrier {
size: usize,
arrived: Pad<AtomicUsize>,
epoch: Pad<AtomicUsize>,
}
impl Barrier {
pub fn new(size: usize) -> Self {
Barrier {
size,
arrived: Pad(AtomicUsize::new(0)),
epoch: Pad(AtomicUsize::new(0)),
}
}
pub fn arrive(
&self,
cancelled: &AtomicBool,
sync: Option<&Parker>,
waiter: &mut Waiter,
) -> bool {
if cancelled.load(Ordering::Acquire) {
return false;
}
if self.size == 1 {
return true;
}
let epoch = self.epoch.0.load(Ordering::Acquire);
if self.arrived.0.fetch_add(1, Ordering::AcqRel) + 1 == self.size {
self.arrived.0.store(0, Ordering::Relaxed);
self.epoch.0.store(epoch.wrapping_add(1), Ordering::SeqCst);
if let Some(sync) = sync {
sync.wake();
}
return true;
}
loop {
if self.epoch.0.load(Ordering::Acquire) != epoch {
return true;
}
if cancelled.load(Ordering::Acquire) {
return false;
}
if waiter.step() == Step::Sleep {
match sync {
Some(sync) => sync.park_while(|| {
self.epoch.0.load(Ordering::SeqCst) == epoch
&& !cancelled.load(Ordering::SeqCst)
}),
None => std::hint::spin_loop(),
}
}
}
}
}