use std::{collections::VecDeque, fmt, task::Poll};
use crate::{
Closed,
lock::Lock,
waiter::{Waiter, WaiterList},
};
#[non_exhaustive]
pub enum PushError<T> {
Full(T),
Closed(T),
}
impl<T> PushError<T> {
pub fn into_inner(self) -> T {
match self {
Self::Full(item) | Self::Closed(item) => item,
}
}
}
impl<T> fmt::Debug for PushError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full(_) => f.debug_tuple("Full").finish_non_exhaustive(),
Self::Closed(_) => f.debug_tuple("Closed").finish_non_exhaustive(),
}
}
}
impl<T> fmt::Display for PushError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full(_) => write!(f, "queue full"),
Self::Closed(_) => write!(f, "queue closed"),
}
}
}
impl<T> std::error::Error for PushError<T> {}
#[derive(Debug)]
struct State<T> {
queue: VecDeque<T>,
capacity: Option<usize>,
closed: bool,
waiters_pop: WaiterList,
waiters_push: WaiterList,
}
impl<T> State<T> {
fn has_space(&self) -> bool {
self.capacity.is_none_or(|capacity| self.queue.len() < capacity)
}
}
#[derive(Debug)]
pub struct Queue<T> {
state: Lock<State<T>>,
}
impl<T> Queue<T> {
pub fn new() -> Self {
Self::with_capacity(None)
}
pub fn bounded(capacity: usize) -> Self {
assert!(capacity > 0, "a zero-capacity Queue could never accept an item");
Self::with_capacity(Some(capacity))
}
fn with_capacity(capacity: Option<usize>) -> Self {
Self {
state: Lock::new(State {
queue: VecDeque::new(),
capacity,
closed: false,
waiters_pop: WaiterList::new(),
waiters_push: WaiterList::new(),
}),
}
}
pub fn try_push(&self, item: T) -> Result<(), PushError<T>> {
let mut waiters = {
let mut state = self.state.lock();
if state.closed {
return Err(PushError::Closed(item));
}
if !state.has_space() {
return Err(PushError::Full(item));
}
state.queue.push_back(item);
state.waiters_pop.take()
};
waiters.wake();
Ok(())
}
pub fn poll_push_with<F: FnOnce() -> T>(&self, waiter: &Waiter, make: F) -> Poll<Result<(), Closed>> {
let mut waiters = {
let mut state = self.state.lock();
if state.closed {
return Poll::Ready(Err(Closed));
}
if !state.has_space() {
waiter.register(&mut state.waiters_push);
return Poll::Pending;
}
let item = make();
state.queue.push_back(item);
state.waiters_pop.take()
};
waiters.wake();
Poll::Ready(Ok(()))
}
pub async fn push(&self, item: T) -> Result<(), Closed> {
let mut item = Some(item);
let slot = &mut item;
crate::wait(move |waiter| self.poll_push_with(waiter, || slot.take().expect("polled after completion"))).await
}
pub fn try_pop(&self) -> Result<Option<T>, Closed> {
let (item, mut waiters) = {
let mut state = self.state.lock();
match state.queue.pop_front() {
Some(item) => (item, state.waiters_push.take()),
None if state.closed => return Err(Closed),
None => return Ok(None),
}
};
waiters.wake();
Ok(Some(item))
}
pub fn poll_pop(&self, waiter: &Waiter) -> Poll<Result<T, Closed>> {
let (item, mut waiters) = {
let mut state = self.state.lock();
match state.queue.pop_front() {
Some(item) => (item, state.waiters_push.take()),
None if state.closed => return Poll::Ready(Err(Closed)),
None => {
waiter.register(&mut state.waiters_pop);
return Poll::Pending;
}
}
};
waiters.wake();
Poll::Ready(Ok(item))
}
pub async fn pop(&self) -> Result<T, Closed> {
crate::wait(move |waiter| self.poll_pop(waiter)).await
}
pub fn close(&self) {
let mut waiters = {
let mut state = self.state.lock();
if state.closed {
return;
}
state.closed = true;
[state.waiters_pop.take(), state.waiters_push.take()]
};
for list in &mut waiters {
list.wake();
}
}
pub fn is_closed(&self) -> bool {
self.state.lock().closed
}
pub fn len(&self) -> usize {
self.state.lock().queue.len()
}
pub fn is_empty(&self) -> bool {
self.state.lock().queue.is_empty()
}
pub fn capacity(&self) -> Option<usize> {
self.state.lock().capacity
}
pub fn same_channel(&self, other: &Self) -> bool {
self.state.is_clone(&other.state)
}
}
impl<T> Default for Queue<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Clone for Queue<T> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}
#[cfg(all(test, not(loom)))]
mod test {
use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
task::{Wake, Waker},
};
use super::*;
struct CountWaker(AtomicUsize);
impl CountWaker {
fn count(&self) -> usize {
self.0.load(Ordering::SeqCst)
}
}
impl Wake for CountWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn counting() -> (Arc<CountWaker>, Waker) {
let waker = Arc::new(CountWaker(AtomicUsize::new(0)));
let w = Waker::from(waker.clone());
(waker, w)
}
#[test]
fn fifo_order() {
let queue = Queue::new();
queue.try_push(1).unwrap();
queue.try_push(2).unwrap();
queue.try_push(3).unwrap();
assert_eq!(queue.try_pop().unwrap(), Some(1));
assert_eq!(queue.try_pop().unwrap(), Some(2));
assert_eq!(queue.try_pop().unwrap(), Some(3));
assert_eq!(queue.try_pop().unwrap(), None, "empty but open");
}
#[test]
fn bounded_rejects_when_full_and_returns_the_item() {
let queue = Queue::bounded(1);
queue.try_push(1).unwrap();
match queue.try_push(2) {
Err(PushError::Full(item)) => assert_eq!(item, 2),
other => panic!("expected Full, got {other:?}"),
}
assert_eq!(queue.try_pop().unwrap(), Some(1));
queue.try_push(2).unwrap();
}
#[test]
fn closed_rejects_pushes_and_drains_pops() {
let queue = Queue::new();
queue.try_push(1).unwrap();
queue.close();
queue.close();
match queue.try_push(2) {
Err(PushError::Closed(item)) => assert_eq!(item, 2),
other => panic!("expected Closed, got {other:?}"),
}
assert_eq!(queue.try_pop().unwrap(), Some(1));
assert_eq!(queue.try_pop(), Err(Closed));
let waiter = Waiter::noop();
assert_eq!(queue.poll_pop(&waiter), Poll::Ready(Err(Closed)));
assert_eq!(queue.poll_push_with(&waiter, || 3), Poll::Ready(Err(Closed)));
}
#[test]
fn push_wakes_a_parked_pop() {
let queue = Queue::new();
let (waker, w) = counting();
let waiter = Waiter::new(w);
assert!(queue.poll_pop(&waiter).is_pending());
queue.try_push(7).unwrap();
assert!(waker.count() >= 1, "push should wake the parked pop");
assert_eq!(queue.poll_pop(&waiter), Poll::Ready(Ok(7)));
}
#[test]
fn pop_wakes_a_parked_push_and_defers_the_item() {
let queue = Queue::bounded(1);
queue.try_push(1).unwrap();
let (waker, w) = counting();
let waiter = Waiter::new(w);
let made = std::cell::Cell::new(false);
assert!(
queue
.poll_push_with(&waiter, || {
made.set(true);
2
})
.is_pending()
);
assert!(!made.get(), "the item must not be built while full");
assert_eq!(queue.try_pop().unwrap(), Some(1));
assert!(waker.count() >= 1, "pop should wake the parked push");
assert_eq!(queue.poll_push_with(&waiter, || 2), Poll::Ready(Ok(())));
assert_eq!(queue.try_pop().unwrap(), Some(2));
}
#[test]
fn close_wakes_both_sides() {
let queue = Queue::<u32>::bounded(1);
queue.try_push(1).unwrap();
let (pop_waker, w1) = counting();
let pop_waiter = Waiter::new(w1);
let popper = queue.clone();
assert_eq!(popper.poll_pop(&pop_waiter), Poll::Ready(Ok(1)));
assert!(popper.poll_pop(&pop_waiter).is_pending());
queue.try_push(2).unwrap();
let (push_waker, w2) = counting();
let push_waiter = Waiter::new(w2);
assert!(queue.poll_push_with(&push_waiter, || 3).is_pending());
queue.close();
assert!(pop_waker.count() >= 1, "close should wake the parked pop");
assert!(push_waker.count() >= 1, "close should wake the parked push");
assert_eq!(popper.poll_pop(&pop_waiter), Poll::Ready(Ok(2)));
assert_eq!(popper.poll_pop(&pop_waiter), Poll::Ready(Err(Closed)));
assert_eq!(queue.poll_push_with(&push_waiter, || 3), Poll::Ready(Err(Closed)));
}
#[test]
fn accessors() {
let queue = Queue::bounded(2);
assert_eq!(queue.capacity(), Some(2));
assert!(queue.is_empty());
queue.try_push(1).unwrap();
assert_eq!(queue.len(), 1);
assert!(!queue.is_empty());
assert!(!queue.is_closed());
let clone = queue.clone();
let other = Queue::<u32>::new();
assert!(queue.same_channel(&clone));
assert!(!queue.same_channel(&other));
assert_eq!(other.capacity(), None);
}
#[test]
#[should_panic(expected = "zero-capacity")]
fn zero_capacity_panics() {
let _ = Queue::<u32>::bounded(0);
}
#[tokio::test]
async fn async_push_parks_until_popped() {
let queue = Queue::bounded(1);
queue.try_push(1u32).unwrap();
let pusher = queue.clone();
let task = tokio::spawn(async move { pusher.push(2).await });
tokio::task::yield_now().await;
assert_eq!(queue.pop().await, Ok(1));
task.await.unwrap().unwrap();
assert_eq!(queue.pop().await, Ok(2));
}
#[tokio::test]
async fn async_pop_parks_until_pushed() {
let queue = Queue::new();
let popper = queue.clone();
let task = tokio::spawn(async move { popper.pop().await });
tokio::task::yield_now().await;
queue.try_push(9u32).unwrap();
assert_eq!(task.await.unwrap(), Ok(9));
}
#[tokio::test]
async fn async_ops_observe_close() {
let queue = Queue::<u32>::bounded(1);
queue.try_push(1).unwrap();
let pusher = queue.clone();
let push = tokio::spawn(async move { pusher.push(2).await });
tokio::task::yield_now().await;
queue.close();
assert_eq!(push.await.unwrap(), Err(Closed));
assert_eq!(queue.pop().await, Ok(1));
assert_eq!(queue.pop().await, Err(Closed));
}
}