use core::{
fmt,
marker::PhantomData,
pin::Pin,
sync::atomic::Ordering::{Acquire, Release, SeqCst},
task::Waker,
};
use crate::{
List, ListRef, Node, NodeData,
linking::{AtomicEager, Linking},
list::{Back, End, Front, LockedList},
loom::sync::atomic::{Ordering::Relaxed, fence},
node::NodeRef,
sync::mutex::{DefaultMutex, Mutex},
wait_list::{
synchronization::{SyncMode, Synchronization, Synchronized},
wait::{Wait, WaitUntil, WakeCondition},
},
waker_batch::WakerBatch,
};
pub mod synchronization;
pub mod wait;
pub const DEFAULT_WAKER_BATCH_SIZE: usize = 32;
const STATE_OPEN: usize = 0;
const STATE_CLOSED: usize = 1;
struct Waiter<N: Notification> {
waker: Option<Waker>,
notification: Option<Notified<N>>,
waiter: N::Waiter,
}
impl<N: Notification> Waiter<N> {
fn new(waiter: N::Waiter) -> Self {
Self {
waker: None,
notification: None,
waiter,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClosedError;
impl fmt::Display for ClosedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("wait list is closed")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ClosedError {}
enum Notified<N> {
One(N),
Last(N),
All(N),
}
impl<N> Notified<N> {
fn into_inner(self) -> N {
match self {
Self::One(notification) | Self::Last(notification) | Self::All(notification) => {
notification
}
}
}
}
pub trait Notification: Unpin {
type Waiter: Unpin;
fn matches(&self, waiter: &Self::Waiter) -> bool;
}
impl Notification for () {
type Waiter = ();
fn matches(&self, _waiter: &Self::Waiter) -> bool {
true
}
}
pub struct WaitList<
N: Notification = (),
S: Synchronization = Synchronized,
L: Linking = AtomicEager,
M: Mutex = DefaultMutex,
const WAKER_BATCH_SIZE: usize = DEFAULT_WAKER_BATCH_SIZE,
> {
list: List<Waiter<N>, usize, (), L, M>,
_synchronization: PhantomData<S>,
}
impl<N: Notification, S: Synchronization, L: Linking, M: Mutex, const WAKER_BATCH_SIZE: usize>
Default for WaitList<N, S, L, M, WAKER_BATCH_SIZE>
{
fn default() -> Self {
Self::new()
}
}
impl<N: Notification, S: Synchronization, L: Linking, M: Mutex, const WAKER_BATCH_SIZE: usize>
WaitList<N, S, L, M, WAKER_BATCH_SIZE>
{
#[cfg_attr(loom, const_fn::const_fn(cfg(false)))]
#[inline]
pub const fn new() -> Self {
Self {
list: List::with_state(STATE_OPEN),
_synchronization: PhantomData,
}
}
#[inline(always)]
fn is_empty(&self) -> bool {
if S::SYNC {
fence(SeqCst);
}
self.list.is_empty(match S::MODE {
SyncMode::Sequential => SeqCst,
_ => Relaxed,
})
}
#[allow(clippy::incompatible_msrv)]
pub fn is_closed(&self) -> bool {
matches!(self.list.load_state(Acquire), Some(s) if s != STATE_OPEN)
}
pub fn close(&self) {
self.list.update_state_or_lock_with(
Release,
Relaxed,
|_| STATE_CLOSED,
|locked| {
Self::wake_all(locked, STATE_CLOSED, || None);
},
);
}
#[cold]
fn wake_all<F: FnMut() -> Option<Notified<N>>>(
locked: LockedList<Waiter<N>, usize, (), L, M>,
state: usize,
mut notification: F,
) -> usize {
locked
.drain(|_| state)
.wake_all::<WAKER_BATCH_SIZE, _>(|mut waiter, _| {
if let Some(notification) = notification() {
waiter.notification = Some(notification);
}
waiter.waker.take()
})
}
#[inline]
pub fn notify_one_with<F: FnOnce() -> N>(&self, notification: F) -> bool {
!self.is_empty() && self.wake_single::<Front, _>(notification)
}
#[inline]
pub fn notify_last_with<F: FnOnce() -> N>(&self, notification: F) -> bool {
!self.is_empty() && self.wake_single::<Back, _>(notification)
}
#[cold]
fn wake_single<E: End, F: FnOnce() -> N>(&self, notification: F) -> bool {
Self::wake_single_locked::<E, F>(self.list.lock(), notification)
}
fn wake_single_locked<E: End, F: FnOnce() -> N>(
mut locked: LockedList<Waiter<N>, usize, (), L, M>,
notification: F,
) -> bool {
let Some(mut waiter) = locked.end::<E>() else {
return false;
};
let notification = notification();
let notified = if E::IS_FRONT {
Notified::One
} else {
Notified::Last
};
let waker = if notification.matches(&waiter.data().waiter) {
waiter.data_mut().notification = Some(notified(notification));
let waker = waiter.data_mut().waker.take();
waiter.unlink(|_, _| STATE_OPEN);
waker
} else {
let mut cursor = waiter.into_cursor();
loop {
if E::IS_FRONT {
cursor.move_next();
} else {
cursor.move_prev();
}
let Some(mut waiter) = cursor.current() else {
return false;
};
if notification.matches(&waiter.waiter) {
waiter.notification = Some(notified(notification));
let waker = waiter.waker.take();
cursor.remove_current(|_, _| STATE_OPEN);
break waker;
}
}
};
drop(locked);
if let Some(waker) = waker {
waker.wake();
}
true
}
#[inline]
pub fn notify_many_with<F: FnMut() -> N>(&self, count: usize, notification: F) -> usize {
if self.is_empty() {
return 0;
}
self.wake_many(count, notification)
}
#[cold]
fn wake_many<F: FnMut() -> N>(&self, count: usize, mut notification: F) -> usize {
let mut wakers = WakerBatch::<WAKER_BATCH_SIZE>::new();
let mut locked = self.list.lock();
let mut cursor = locked.cursor_front();
let mut notified = 0;
while notified < count {
let Some(mut waiter) = cursor.current() else {
break;
};
let notification = notification();
if !notification.matches(&waiter.waiter) {
cursor.move_next();
continue;
}
waiter.notification = Some(Notified::One(notification));
if let Some(waker) = waiter.waker.take() {
wakers.push(waker);
}
cursor.remove_current(|_, _| STATE_OPEN);
notified += 1;
if wakers.is_full() {
let list = locked.unlock();
wakers.wake_all();
if list.is_empty(Relaxed) {
return notified;
}
locked = list.lock();
cursor = locked.cursor_front();
}
}
drop(locked);
wakers.wake_all();
notified
}
#[inline]
pub fn notify_all_with<F: FnMut() -> N>(&self, notification: F) -> usize {
if self.is_empty() {
return 0;
}
self.notify_all_impl(notification)
}
#[cold]
fn notify_all_impl<F: FnMut() -> N>(&self, mut notification: F) -> usize {
let locked = self.list.lock();
Self::wake_all(locked, STATE_OPEN, || Some(Notified::All(notification())))
}
#[inline]
pub fn wait_with(&self, waiter: N::Waiter) -> Wait<'_, N, S, L, M, WAKER_BATCH_SIZE> {
Wait(Node::with_data(WaitListRef(self), Waiter::new(waiter)))
}
#[inline]
pub fn wait_until_with<F: FnMut(bool) -> W, G: FnMut(N) -> W, W: WakeCondition>(
&self,
waiter: N::Waiter,
wake_condition: F,
on_notification: G,
) -> WaitUntil<'_, F, G, N, S, L, M, WAKER_BATCH_SIZE> {
WaitUntil::new(self.wait_with(waiter), wake_condition, on_notification)
}
#[cold]
fn renotify(&self, notification: Notified<N>) {
match notification {
Notified::One(notification) => self.notify_one_with(|| notification),
Notified::Last(notification) => self.notify_last_with(|| notification),
_ => unreachable!(),
};
}
}
impl<S: Synchronization, L: Linking, M: Mutex, const WAKER_BATCH_SIZE: usize>
WaitList<(), S, L, M, WAKER_BATCH_SIZE>
{
#[inline]
pub fn notify_one(&self) -> bool {
self.notify_one_with(|| ())
}
#[inline]
pub fn notify_last(&self) -> bool {
self.notify_last_with(|| ())
}
#[inline]
pub fn notify_many(&self, count: usize) -> usize {
self.notify_many_with(count, || ())
}
#[inline]
pub fn notify_all(&self) -> usize {
self.notify_all_with(|| ())
}
#[inline]
pub fn wait(&self) -> Wait<'_, (), S, L, M, WAKER_BATCH_SIZE> {
self.wait_with(())
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn wait_until<F: FnMut(bool) -> W, W: WakeCondition>(
&self,
wake_condition: F,
) -> WaitUntil<'_, F, impl FnMut(()) -> W, (), S, L, M, WAKER_BATCH_SIZE> {
self.wait_until_with((), wake_condition, |_| W::default())
}
}
struct WaitListRef<
'a,
N: Notification,
S: Synchronization,
L: Linking,
M: Mutex,
const WAKER_BATCH_SIZE: usize,
>(&'a WaitList<N, S, L, M, WAKER_BATCH_SIZE>);
impl<N: Notification, S: Synchronization, L: Linking, M: Mutex, const WAKER_BATCH_SIZE: usize>
ListRef for WaitListRef<'_, N, S, L, M, WAKER_BATCH_SIZE>
{
type NodeData = Waiter<N>;
type ListState = usize;
type ListData = ();
type Linking = L;
type Mutex = M;
fn as_list(&self) -> &List<Waiter<N>, usize, (), L, M> {
&self.0.list
}
}
impl<'a, N: Notification, S: Synchronization, L: Linking, M: Mutex, const WAKER_BATCH_SIZE: usize>
NodeData<WaitListRef<'a, N, S, L, M, WAKER_BATCH_SIZE>> for Waiter<N>
{
#[inline]
fn new_state_if_last_node_on_drop(
self: Pin<&mut Self>,
_list: &WaitListRef<'a, N, S, L, M, WAKER_BATCH_SIZE>,
_list_data: &mut (),
) -> usize {
STATE_OPEN
}
#[inline(always)]
fn on_drop<'list>(
self: Pin<&mut Self>,
list: &'list WaitListRef<'a, N, S, L, M, WAKER_BATCH_SIZE>,
locked: Option<LockedList<'list, Self, usize, (), L, M>>,
state_updated_on_unlink: bool,
) {
if !matches!(
&self.notification,
Some(Notified::One(_) | Notified::Last(_))
) {
return;
}
let notif = self.get_mut().notification.take().unwrap();
if let Some(locked) = locked {
debug_assert!(!state_updated_on_unlink);
match notif {
Notified::One(notification) => {
WaitList::<N, S, L, M, WAKER_BATCH_SIZE>::wake_single_locked::<Front, _>(
locked,
|| notification,
);
}
Notified::Last(notification) => {
WaitList::<N, S, L, M, WAKER_BATCH_SIZE>::wake_single_locked::<Back, _>(
locked,
|| notification,
);
}
_ => unreachable!(),
}
} else {
list.0.renotify(notif);
}
}
}