use tokio::sync::mpsc;
pub fn channel<W>(capacity: usize) -> (Mailbox<W>, Inbox<W>) {
assert!(capacity > 0, "a mailbox needs capacity");
let (sender, receiver) = mpsc::channel(capacity);
(Mailbox { inner: sender }, Inbox { inner: receiver })
}
#[repr(transparent)]
pub struct Mailbox<W> {
inner: mpsc::Sender<W>,
}
impl<W> Clone for Mailbox<W> {
fn clone(&self) -> Self {
Self { inner: self.inner.clone() }
}
}
impl<W> std::fmt::Debug for Mailbox<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mailbox").field("capacity", &self.inner.max_capacity()).finish()
}
}
impl<W> Mailbox<W> {
#[inline]
pub async fn send(&self, item: W) -> Result<(), Closed<W>> {
self.inner.send(item).await.map_err(|error| Closed(error.0))
}
#[inline]
pub fn try_send(&self, item: W) -> Result<(), TrySendError<W>> {
match self.inner.try_send(item) {
Ok(()) => Ok(()),
Err(mpsc::error::TrySendError::Full(item)) => Err(TrySendError::Full(item)),
Err(mpsc::error::TrySendError::Closed(item)) => Err(TrySendError::Closed(item)),
}
}
}
#[repr(transparent)]
pub struct Inbox<W> {
inner: mpsc::Receiver<W>,
}
impl<W> std::fmt::Debug for Inbox<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Inbox").finish_non_exhaustive()
}
}
impl<W> Inbox<W> {
#[inline]
pub async fn recv(&mut self) -> Option<W> {
self.inner.recv().await
}
#[inline]
pub(crate) fn poll_recv(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<W>> {
self.inner.poll_recv(cx)
}
#[inline]
pub fn try_recv(&mut self) -> Result<W, TryRecvError> {
self.inner.try_recv().map_err(|error| match error {
mpsc::error::TryRecvError::Empty => TryRecvError::Empty,
mpsc::error::TryRecvError::Disconnected => TryRecvError::Closed,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Closed<W>(pub W);
impl<W> Closed<W> {
pub fn into_inner(self) -> W {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TrySendError<W> {
Full(W),
Closed(W),
}
impl<W> TrySendError<W> {
pub fn into_inner(self) -> W {
match self {
Self::Full(item) | Self::Closed(item) => item,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TryRecvError {
Empty,
Closed,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn items_arrive_in_submission_order() {
let (mailbox, mut inbox) = channel(4);
for item in 0..3 {
mailbox.send(item).await.unwrap();
}
assert_eq!(inbox.recv().await, Some(0));
assert_eq!(inbox.recv().await, Some(1));
assert_eq!(inbox.recv().await, Some(2));
}
#[tokio::test]
async fn a_full_mailbox_hands_the_item_back_rather_than_dropping_it() {
let (mailbox, mut inbox) = channel(1);
mailbox.try_send(1).unwrap();
assert_eq!(
mailbox.try_send(2),
Err(TrySendError::Full(2)),
"the caller gets its work back"
);
assert_eq!(inbox.recv().await, Some(1));
mailbox.try_send(2).unwrap();
assert_eq!(inbox.recv().await, Some(2));
}
#[tokio::test]
async fn a_departed_shard_is_reported_by_both_submission_paths() {
let (mailbox, inbox) = channel::<u8>(4);
drop(inbox);
assert_eq!(mailbox.try_send(1), Err(TrySendError::Closed(1)));
assert_eq!(mailbox.send(2).await, Err(Closed(2)));
assert_eq!(Closed(3).into_inner(), 3);
assert_eq!(TrySendError::Full(4).into_inner(), 4);
}
#[tokio::test]
async fn an_immediate_receive_separates_an_empty_queue_from_a_closed_one() {
let (mailbox, mut inbox) = channel(4);
assert_eq!(inbox.try_recv(), Err(TryRecvError::Empty), "senders remain, so this is a lull");
mailbox.send(9).await.unwrap();
assert_eq!(inbox.try_recv(), Ok(9));
drop(mailbox);
assert_eq!(inbox.try_recv(), Err(TryRecvError::Closed));
assert_eq!(inbox.recv().await, None);
}
#[tokio::test]
async fn a_drained_mailbox_still_delivers_what_was_already_queued() {
let (mailbox, mut inbox) = channel(4);
mailbox.send(1).await.unwrap();
mailbox.send(2).await.unwrap();
drop(mailbox);
assert_eq!(inbox.try_recv(), Ok(1));
assert_eq!(inbox.recv().await, Some(2));
assert_eq!(inbox.recv().await, None);
}
#[test]
#[should_panic(expected = "a mailbox needs capacity")]
fn a_zero_capacity_mailbox_is_refused() {
let _ = channel::<u8>(0);
}
#[test]
fn the_halves_are_the_size_of_the_channel_they_wrap() {
use std::mem::size_of;
assert_eq!(size_of::<Mailbox<u64>>(), size_of::<mpsc::Sender<u64>>());
assert_eq!(size_of::<Inbox<u64>>(), size_of::<mpsc::Receiver<u64>>());
}
}