#![cfg_attr(not(test), no_std)]
#![warn(
elided_lifetimes_in_paths,
explicit_outlives_requirements,
missing_debug_implementations,
missing_docs,
semicolon_in_expressions_from_macros,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_qualifications
)]
#![warn(clippy::undocumented_unsafe_blocks)]
use core::future::Future;
use core::{
cell::Cell,
marker::PhantomPinned,
pin::Pin,
ptr::NonNull,
task::{Poll, Waker},
};
use pin_project::{pin_project, pinned_drop};
#[derive(Default)]
pub struct List<T> {
root: Cell<Option<Root<T>>>,
_marker: PhantomPinned,
}
impl<T> List<T> {
pub const fn new() -> Self {
Self {
root: Cell::new(None),
_marker: PhantomPinned,
}
}
pub fn is_empty(&self) -> bool {
self.root.get().is_none()
}
pub fn wake_while(
self: Pin<&Self>,
mut pred: impl FnMut(&T) -> bool,
) -> bool {
let mut any = false;
loop {
if !self.wake_head_if(&mut pred) {
break;
}
any = true;
}
any
}
pub fn wake_all(self: Pin<&Self>) -> bool {
self.wake_while(|_| true)
}
pub fn wake_head_if(
self: Pin<&Self>,
pred: impl FnOnce(&T) -> bool,
) -> bool {
let Some(root) = self.root.get() else {
return false;
};
let node_ptr = root.head;
let node = unsafe { Pin::new_unchecked(&*node_ptr.as_ptr()) };
debug_assert_eq!(node.list.get(), Some(NonNull::from(&*self)));
debug_assert!(node.prev.get().is_none());
if !pred(&node.contents) {
return false;
}
if node_ptr == root.tail {
debug_assert_eq!(node.next.get(), None,
"list thinks node @{node_ptr:?} is tail, \
node thinks it has a next");
self.root.set(None);
} else {
let Some(next_ptr) = node.next.take() else {
panic!()
};
let next = unsafe { Pin::new_unchecked(next_ptr.as_ref()) };
next.prev.set(None);
self.root.set(Some(Root {
head: next_ptr,
..root
}));
}
node.list.take();
if let Some(waker) = node.waker.take() {
waker.wake();
} else {
panic!();
}
true
}
pub fn wake_one(self: Pin<&Self>) -> bool {
self.wake_head_if(|_| true)
}
pub fn join(
self: Pin<&Self>,
contents: T,
) -> impl Future<Output = ()> + Captures<&'_ Self>
where
T: PartialOrd,
{
WaitForDetach {
list: Some(self),
node: Node {
prev: Cell::new(None),
next: Cell::new(None),
waker: Cell::new(None),
list: Cell::new(None),
contents,
_marker: PhantomPinned,
},
}
}
pub fn join_with_cleanup(
self: Pin<&Self>,
contents: T,
cleanup: impl FnOnce(),
) -> impl Future<Output = ()> + Captures<Pin<&'_ Self>>
where
T: PartialOrd,
{
let inner = WaitForDetach {
list: Some(self),
node: Node {
prev: Cell::new(None),
next: Cell::new(None),
waker: Cell::new(None),
list: Cell::new(None),
contents,
_marker: PhantomPinned,
},
};
WaitWithCleanup {
inner,
cleanup: Some(cleanup),
}
}
}
#[pin_project]
struct WaitForDetach<'list, T> {
list: Option<Pin<&'list List<T>>>,
#[pin]
node: Node<T>,
}
impl<T: PartialOrd> Future for WaitForDetach<'_, T> {
type Output = ();
fn poll(
self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> Poll<Self::Output> {
let p = self.project();
let node = p.node.into_ref();
let node_ptr = NonNull::from(&*node);
if let Some(list) = p.list.take() {
if let Some(mut root) = list.root.get() {
let mut maybe_cand = Some(root.tail);
while let Some(cand_ptr) = maybe_cand {
let candidate =
unsafe { Pin::new_unchecked(cand_ptr.as_ref()) };
if candidate.contents <= node.contents {
let old_next = candidate.next.replace(Some(node_ptr));
if let Some(next_ptr) = old_next {
let next = unsafe {
Pin::new_unchecked(next_ptr.as_ref())
};
next.prev.set(Some(node_ptr));
}
node.next.set(old_next);
node.prev.set(Some(cand_ptr));
if cand_ptr == root.tail {
root.tail = node_ptr;
list.root.set(Some(root));
}
break;
}
maybe_cand = candidate.prev.get();
}
if maybe_cand.is_none() {
let old_head =
unsafe { Pin::new_unchecked(root.head.as_ref()) };
old_head.prev.set(Some(node_ptr));
node.next.set(Some(root.head));
root.head = node_ptr;
list.root.set(Some(root));
}
} else {
list.root.set(Some(Root {
head: node_ptr,
tail: node_ptr,
}));
}
node.list.set(Some(NonNull::from(&*list)));
node.waker.set(Some(cx.waker().clone()));
Poll::Pending
} else {
if node.list.get().is_none() {
Poll::Ready(())
} else {
node.waker.set(Some(cx.waker().clone()));
Poll::Pending
}
}
}
}
#[pin_project(PinnedDrop)]
struct WaitWithCleanup<'list, T, F: FnOnce()> {
#[pin]
inner: WaitForDetach<'list, T>,
cleanup: Option<F>,
}
#[pinned_drop]
impl<T, F: FnOnce()> PinnedDrop for WaitWithCleanup<'_, T, F> {
fn drop(self: Pin<&mut Self>) {
let p = self.project();
let pi = p.inner.project();
let node = pi.node.into_ref();
if pi.list.is_none() && node.list.get().is_none() {
if let Some(cleanup) = p.cleanup.take() {
cleanup();
}
}
}
}
impl<T: PartialOrd, F: FnOnce()> Future for WaitWithCleanup<'_, T, F> {
type Output = ();
fn poll(
self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> Poll<Self::Output> {
let p = self.project();
if p.inner.poll(cx).is_ready() {
p.cleanup.take();
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl<T> core::fmt::Debug for List<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("List").field("root", &self.root).finish()
}
}
#[cfg(debug_assertions)]
impl<T> Drop for List<T> {
fn drop(&mut self) {
debug_assert!(self.root.get().is_none());
}
}
struct Root<T> {
head: NonNull<Node<T>>,
tail: NonNull<Node<T>>,
}
impl<T> Copy for Root<T> {}
impl<T> Clone for Root<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> core::fmt::Debug for Root<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Root")
.field("head", &self.head)
.field("tail", &self.tail)
.finish()
}
}
struct Node<T> {
prev: Cell<Option<NonNull<Self>>>,
next: Cell<Option<NonNull<Self>>>,
waker: Cell<Option<Waker>>,
contents: T,
list: Cell<Option<NonNull<List<T>>>>,
_marker: PhantomPinned,
}
impl<T> Drop for Node<T> {
fn drop(&mut self) {
if let Some(list_ptr) = self.list.take() {
let list = unsafe { Pin::new_unchecked(list_ptr.as_ref()) };
if let Some(prev_ptr) = self.prev.get() {
let prev = unsafe { Pin::new_unchecked(prev_ptr.as_ref()) };
prev.next.set(self.next.get());
}
if let Some(next_ptr) = self.next.get() {
let next = unsafe { Pin::new_unchecked(next_ptr.as_ref()) };
next.prev.set(self.prev.get());
}
match (self.prev.get(), self.next.get()) {
(None, None) => {
list.root.set(None);
}
(Some(prev_ptr), None) => {
list.root.set(Some(Root {
tail: prev_ptr,
..list.root.get().unwrap()
}));
}
(None, Some(next_ptr)) => {
list.root.set(Some(Root {
head: next_ptr,
..list.root.get().unwrap()
}));
}
(Some(_), Some(_)) => {
}
}
}
debug_assert_eq!(self.list.get(), None);
}
}
#[derive(Copy, Clone, Debug)]
pub struct OrderAndMeta<T, M>(pub T, pub M);
impl<T: PartialEq, M> PartialEq for OrderAndMeta<T, M> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<T: PartialOrd, M> PartialOrd for OrderAndMeta<T, M> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
#[derive(Copy, Clone, Debug)]
pub struct Meta<M>(pub M);
impl<M> PartialEq for Meta<M> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<M> PartialOrd for Meta<M> {
fn partial_cmp(&self, _other: &Self) -> Option<core::cmp::Ordering> {
Some(core::cmp::Ordering::Equal)
}
}
pub trait Captures<T> {}
impl<U: ?Sized, T> Captures<T> for U {}
#[cfg(test)]
mod tests {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::undocumented_unsafe_blocks)]
use core::{
cell::Cell,
future::Future,
mem::forget,
pin::pin,
sync::atomic::{AtomicUsize, Ordering},
task::{Context, RawWaker, RawWakerVTable, Waker},
};
use std::sync::Arc;
use crate::List;
static VTABLE: RawWakerVTable = RawWakerVTable::new(
|p| {
let arc = unsafe { Arc::from_raw(p as *const AtomicUsize) };
let second_arc = Arc::clone(&arc);
forget(arc);
RawWaker::new(Arc::into_raw(second_arc) as *const (), &VTABLE)
},
|p| {
let arc = unsafe { Arc::from_raw(p as *const AtomicUsize) };
arc.fetch_add(1, Ordering::Relaxed);
},
|p| {
let arc = unsafe { Arc::from_raw(p as *const AtomicUsize) };
arc.fetch_add(1, Ordering::Relaxed);
forget(arc);
},
|p| {
let _arc = unsafe { Arc::from_raw(p as *const AtomicUsize) };
},
);
fn spy_waker() -> (Arc<AtomicUsize>, Waker) {
let count = Arc::new(AtomicUsize::new(0));
let second_count = Arc::clone(&count);
(count, unsafe {
Waker::from_raw(RawWaker::new(
Arc::into_raw(second_count) as *const (),
&VTABLE,
))
})
}
#[test]
fn test_create_drop() {
List::<()>::new();
}
#[test]
fn test_single_node_wait_resume() {
let list = pin!(List::<()>::new());
let list = list.as_ref();
let (wake_count, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
let mut wait_fut = pin!(list.join(()));
assert!(list.is_empty());
assert!(wait_fut.as_mut().poll(&mut ctx).is_pending());
assert_eq!(wake_count.load(Ordering::Relaxed), 0);
assert!(!list.is_empty());
assert!(wait_fut.as_mut().poll(&mut ctx).is_pending());
assert!(wait_fut.as_mut().poll(&mut ctx).is_pending());
assert!(list.wake_head_if(|_| true));
assert_eq!(wake_count.load(Ordering::Relaxed), 1);
assert!(list.is_empty());
assert!(wait_fut.poll(&mut ctx).is_ready());
assert_eq!(wake_count.load(Ordering::Relaxed), 1);
}
#[test]
fn test_single_node_drop_while_in_list() {
let list = pin!(List::<()>::new());
let list = list.as_ref();
let (wake_count, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
{
let mut wait_fut = pin!(list.join(()));
assert!(wait_fut.as_mut().poll(&mut ctx).is_pending());
assert!(!list.is_empty());
}
assert!(list.is_empty());
assert_eq!(wake_count.load(Ordering::Relaxed), 0);
}
#[test]
fn test_wake_while_insert_order() {
let list = pin!(List::new());
let list = list.into_ref();
let (wake_count, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
let mut fut_a = pin!(list.join(()));
assert!(fut_a.as_mut().poll(&mut ctx).is_pending());
let mut fut_b = pin!(list.join(()));
assert!(fut_b.as_mut().poll(&mut ctx).is_pending());
let mut fut_c = pin!(list.join(()));
assert!(fut_c.as_mut().poll(&mut ctx).is_pending());
let mut fut_d = pin!(list.join(()));
assert!(fut_d.as_mut().poll(&mut ctx).is_pending());
let mut check_count = 0;
let changes = list.wake_while(|_n| {
check_count += 1;
check_count <= 2
});
assert!(changes);
assert_eq!(check_count, 3);
assert_eq!(wake_count.load(Ordering::Relaxed), 2);
assert!(fut_a.as_mut().poll(&mut ctx).is_ready());
assert!(fut_b.as_mut().poll(&mut ctx).is_ready());
assert!(fut_c.as_mut().poll(&mut ctx).is_pending());
assert!(fut_d.as_mut().poll(&mut ctx).is_pending());
}
#[test]
fn test_insert_and_wait_cancel_behavior() {
let list = pin!(List::new());
let list = list.into_ref();
let fut = list.join(());
drop(fut);
{
let (_, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
let fut = pin!(list.join(()));
let _ = fut.poll(&mut ctx); }
}
#[test]
fn test_iawwc_no_fire_if_never_polled() {
let list = pin!(List::new());
let list = list.into_ref();
let cleanup_called = Cell::new(false);
let fut = list.join_with_cleanup((), || cleanup_called.set(true));
assert!(!cleanup_called.get());
drop(fut);
assert!(!cleanup_called.get());
}
#[test]
fn test_iawwc_no_fire_if_polled_after_detach() {
let list = pin!(List::new());
let list = list.into_ref();
let (_, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
let cleanup_called = Cell::new(false);
{
let mut fut =
pin!(list.join_with_cleanup((), || cleanup_called.set(true),));
assert!(!cleanup_called.get());
let _ = fut.as_mut().poll(&mut ctx);
assert!(list.wake_one());
let _ = fut.poll(&mut ctx);
}
assert!(!cleanup_called.get());
}
#[test]
fn test_iawwc_fire() {
let list = pin!(List::new());
let list = list.into_ref();
let (_, waker) = spy_waker();
let mut ctx = Context::from_waker(&waker);
let cleanup_called = Cell::new(false);
{
let mut fut =
pin!(list.join_with_cleanup((), || cleanup_called.set(true),));
let _ = fut.as_mut().poll(&mut ctx);
assert_eq!(cleanup_called.get(), false);
assert!(list.wake_one());
assert_eq!(cleanup_called.get(), false);
}
assert_eq!(cleanup_called.get(), true); }
}