use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
struct Waiter {
id: usize,
selected: Rc<Cell<Selection>>,
waker: Waker,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Selection {
Pending,
One,
Waiters,
}
pub struct Notify {
permit: Cell<bool>,
next_waiter_id: Cell<usize>,
waiters: RefCell<VecDeque<Waiter>>,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Notify {
pub fn new() -> Self {
Self {
permit: Cell::new(false),
next_waiter_id: Cell::new(0),
waiters: RefCell::new(VecDeque::new()),
_not_send_sync: PhantomData,
}
}
pub async fn notified(&self) {
Notified::new(self).await
}
pub fn notify_one(&self) {
if let Some(waiter) = self.waiters.borrow_mut().pop_front() {
waiter.selected.set(Selection::One);
waiter.waker.wake();
} else {
self.permit.set(true);
}
}
pub fn notify_waiters(&self) {
for waiter in self.waiters.borrow_mut().drain(..) {
waiter.selected.set(Selection::Waiters);
waiter.waker.wake();
}
}
fn allocate_waiter_id(&self) -> usize {
let id = self.next_waiter_id.get();
self.next_waiter_id.set(id.wrapping_add(1));
id
}
fn remove_waiter(&self, id: usize) {
let mut waiters = self.waiters.borrow_mut();
if let Some(index) = waiters.iter().position(|waiter| waiter.id == id) {
waiters.remove(index);
}
}
}
impl Default for Notify {
fn default() -> Self {
Self::new()
}
}
struct Notified<'a> {
notify: &'a Notify,
waiter: Option<(usize, Rc<Cell<Selection>>)>,
done: bool,
}
impl<'a> Notified<'a> {
fn new(notify: &'a Notify) -> Self {
Self {
notify,
waiter: None,
done: false,
}
}
}
impl Future for Notified<'_> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self
.waiter
.as_ref()
.is_some_and(|(_, selected)| selected.get() != Selection::Pending)
{
self.done = true;
return Poll::Ready(());
}
if self.waiter.is_none() && self.notify.permit.replace(false) {
self.done = true;
return Poll::Ready(());
}
if let Some((id, _)) = &self.waiter {
if let Some(waiter) = self
.notify
.waiters
.borrow_mut()
.iter_mut()
.find(|waiter| waiter.id == *id)
{
waiter.waker = cx.waker().clone();
}
} else {
let id = self.notify.allocate_waiter_id();
let selected = Rc::new(Cell::new(Selection::Pending));
self.notify.waiters.borrow_mut().push_back(Waiter {
id,
selected: Rc::clone(&selected),
waker: cx.waker().clone(),
});
self.waiter = Some((id, selected));
}
Poll::Pending
}
}
impl Drop for Notified<'_> {
fn drop(&mut self) {
if self.done {
return;
}
if let Some((id, selected)) = &self.waiter {
match selected.get() {
Selection::Pending => self.notify.remove_waiter(*id),
Selection::One => self.notify.notify_one(),
Selection::Waiters => {}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use crate::{run, spawn, yield_now};
#[test]
fn fast_path_consumes_stored_permit() {
let notify = Rc::new(Notify::new());
let observed = Rc::new(Cell::new(false));
notify.notify_one();
spawn({
let notify = Rc::clone(¬ify);
let observed = Rc::clone(&observed);
async move {
notify.notified().await;
observed.set(true);
}
});
run();
assert!(observed.get());
}
#[test]
fn contention_wakes_waiters_fifo() {
let notify = Rc::new(Notify::new());
let order = Rc::new(RefCell::new(Vec::new()));
for id in [1, 2] {
spawn({
let notify = Rc::clone(¬ify);
let order = Rc::clone(&order);
async move {
notify.notified().await;
order.borrow_mut().push(id);
}
});
}
spawn({
let notify = Rc::clone(¬ify);
async move {
yield_now().await;
notify.notify_one();
yield_now().await;
notify.notify_one();
}
});
run();
assert_eq!(&*order.borrow(), &[1, 2]);
}
#[test]
fn dropped_notify_one_recipient_forwards_to_next_waiter() {
use std::task::{Context, Waker};
let notify = Notify::new();
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let mut first = Box::pin(notify.notified());
let mut second = Box::pin(notify.notified());
assert!(first.as_mut().poll(&mut cx).is_pending());
assert!(second.as_mut().poll(&mut cx).is_pending());
notify.notify_one();
drop(first);
assert!(second.as_mut().poll(&mut cx).is_ready());
}
#[test]
fn dropped_notify_one_recipient_with_no_waiter_stores_permit() {
use std::task::{Context, Waker};
let notify = Notify::new();
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let mut only = Box::pin(notify.notified());
assert!(only.as_mut().poll(&mut cx).is_pending());
notify.notify_one();
drop(only);
let mut next = Box::pin(notify.notified());
assert!(next.as_mut().poll(&mut cx).is_ready());
}
#[test]
fn dropped_broadcast_recipient_does_not_store_permit() {
use std::task::{Context, Waker};
let notify = Notify::new();
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let mut waiter = Box::pin(notify.notified());
assert!(waiter.as_mut().poll(&mut cx).is_pending());
notify.notify_waiters();
drop(waiter);
let mut next = Box::pin(notify.notified());
assert!(next.as_mut().poll(&mut cx).is_pending());
}
}