use crate::raw_mutex::{RawMutex, TOKEN_HANDOFF, TOKEN_NORMAL};
use crate::{deadlock, util};
use core::{
ptr,
sync::atomic::{AtomicPtr, Ordering},
};
use lock_api::RawMutex as RawMutex_;
use parking_lot_core::{self, ParkResult, RequeueOp, UnparkResult, DEFAULT_PARK_TOKEN};
use std::time::{Duration, Instant};
pub struct RawCondvar {
state: AtomicPtr<RawMutex>,
}
unsafe impl lock_api::RawCondvar for RawCondvar {
const INIT: Self = RawCondvar {
state: AtomicPtr::new(ptr::null_mut()),
};
type RawMutex = RawMutex;
unsafe fn wait(&self, mutex: &RawMutex) {
self.wait_until_internal(mutex, None);
}
#[inline]
fn notify_one(&self) -> bool {
let state = self.state.load(Ordering::Relaxed);
if state.is_null() {
return false;
}
self.notify_one_slow(state)
}
#[inline]
fn notify_all(&self) -> usize {
let state = self.state.load(Ordering::Relaxed);
if state.is_null() {
return 0;
}
self.notify_all_slow(state)
}
}
unsafe impl lock_api::RawCondvarTimed for RawCondvar {
fn checked_duration_to_instant(timeout: &Duration) -> Option<Instant> {
util::to_deadline(*timeout)
}
unsafe fn wait_for(&self, mutex: &RawMutex, timeout: &Duration) -> bool {
let deadline = util::to_deadline(*timeout);
self.wait_until_internal(mutex, deadline)
}
unsafe fn wait_until(&self, mutex: &RawMutex, timeout: &Instant) -> bool {
self.wait_until_internal(mutex, Some(*timeout))
}
}
impl RawCondvar {
#[cold]
fn notify_one_slow(&self, mutex: *mut RawMutex) -> bool {
let from = self as *const _ as usize;
let to = mutex as usize;
let validate = || {
if self.state.load(Ordering::Relaxed) != mutex {
return RequeueOp::Abort;
}
if unsafe { (*mutex).mark_parked_if_locked() } {
RequeueOp::RequeueOne
} else {
RequeueOp::UnparkOne
}
};
let callback = |_op, result: UnparkResult| {
if !result.have_more_threads {
self.state.store(ptr::null_mut(), Ordering::Relaxed);
}
TOKEN_NORMAL
};
let res = unsafe { parking_lot_core::unpark_requeue(from, to, validate, callback) };
res.unparked_threads + res.requeued_threads != 0
}
#[cold]
fn notify_all_slow(&self, mutex: *mut RawMutex) -> usize {
let from = self as *const _ as usize;
let to = mutex as usize;
let validate = || {
if self.state.load(Ordering::Relaxed) != mutex {
return RequeueOp::Abort;
}
self.state.store(ptr::null_mut(), Ordering::Relaxed);
if unsafe { (*mutex).mark_parked_if_locked() } {
RequeueOp::RequeueAll
} else {
RequeueOp::UnparkOneRequeueRest
}
};
let callback = |op, result: UnparkResult| {
if op == RequeueOp::UnparkOneRequeueRest && result.requeued_threads != 0 {
unsafe { (*mutex).mark_parked() };
}
TOKEN_NORMAL
};
let res = unsafe { parking_lot_core::unpark_requeue(from, to, validate, callback) };
res.unparked_threads + res.requeued_threads
}
fn wait_until_internal(&self, mutex: &RawMutex, timeout: Option<Instant>) -> bool {
let result;
let mut bad_mutex = false;
let mut requeued = false;
{
let addr = self as *const _ as usize;
let lock_addr = mutex as *const _ as *mut _;
let validate = || {
let state = self.state.load(Ordering::Relaxed);
if state.is_null() {
self.state.store(lock_addr, Ordering::Relaxed);
} else if state != lock_addr {
bad_mutex = true;
return false;
}
true
};
let before_sleep = || {
unsafe { mutex.unlock() };
};
let timed_out = |k, was_last_thread| {
requeued = k != addr;
if !requeued && was_last_thread {
self.state.store(ptr::null_mut(), Ordering::Relaxed);
}
};
result = unsafe {
parking_lot_core::park(
addr,
validate,
before_sleep,
timed_out,
DEFAULT_PARK_TOKEN,
timeout,
)
};
}
if bad_mutex {
panic!("attempted to use a condition variable with more than one mutex");
}
if result == ParkResult::Unparked(TOKEN_HANDOFF) {
unsafe { deadlock::acquire_resource(mutex as *const _ as usize) };
} else {
mutex.lock();
}
!(result.is_unparked() || requeued)
}
}