#[cfg(target_arch = "wasm32")]
use crate::tokio;
use std::sync::Arc;
use tokio::sync::{Notify, mpsc};
use crate::types::InboxItem;
const DEFAULT_INBOX_CAPACITY: usize = 1024;
pub struct Inbox {
rx: mpsc::Receiver<InboxItem>,
notify: Arc<Notify>,
}
#[derive(Clone)]
pub struct InboxSender {
tx: mpsc::Sender<InboxItem>,
notify: Arc<Notify>,
}
impl Inbox {
pub fn new() -> (Self, InboxSender) {
Self::new_with_capacity(DEFAULT_INBOX_CAPACITY)
}
pub fn new_with_capacity(capacity: usize) -> (Self, InboxSender) {
let (tx, rx) = mpsc::channel(capacity);
let notify = Arc::new(Notify::new());
(
Inbox {
rx,
notify: notify.clone(),
},
InboxSender { tx, notify },
)
}
pub fn notify(&self) -> Arc<Notify> {
self.notify.clone()
}
pub async fn recv(&mut self) -> Option<InboxItem> {
self.rx.recv().await
}
pub fn try_drain(&mut self) -> Vec<InboxItem> {
let mut items = Vec::new();
while let Ok(item) = self.rx.try_recv() {
items.push(item);
}
items
}
}
impl InboxSender {
pub fn send(&self, item: InboxItem) -> Result<(), InboxError> {
self.tx.try_send(item).map_err(|err| match err {
mpsc::error::TrySendError::Closed(_) => InboxError::Closed,
mpsc::error::TrySendError::Full(_) => InboxError::Full,
})?;
self.notify.notify_waiters();
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum InboxError {
#[error("Inbox has been closed")]
Closed,
#[error("Inbox is full")]
Full,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use crate::identity::PubKey;
use crate::types::{Envelope, MessageKind};
use uuid::Uuid;
fn make_test_envelope() -> Envelope {
Envelope {
id: Uuid::new_v4(),
from: PubKey::new([1u8; 32]),
to: PubKey::new([2u8; 32]),
kind: MessageKind::Message {
body: "test".to_string(),
},
sig: crate::identity::Signature::new([0u8; 64]),
}
}
#[test]
fn test_inbox_struct() {
let (inbox, _sender) = Inbox::new();
drop(inbox);
}
#[test]
fn test_inbox_sender_struct() {
let (_inbox, sender) = Inbox::new();
let _sender2 = sender;
}
#[test]
fn test_inbox_new() {
let (inbox, sender) = Inbox::new();
drop(inbox);
drop(sender);
}
#[tokio::test]
async fn test_inbox_sender_send() {
let (_inbox, sender) = Inbox::new();
let item = InboxItem::External {
envelope: make_test_envelope(),
};
let result = sender.send(item);
assert!(result.is_ok());
}
#[tokio::test]
async fn test_inbox_recv() {
let (mut inbox, sender) = Inbox::new();
let envelope = make_test_envelope();
let envelope_id = envelope.id;
sender.send(InboxItem::External { envelope }).unwrap();
let received = inbox.recv().await;
assert!(received.is_some());
match received.unwrap() {
InboxItem::External { envelope } => {
assert_eq!(envelope.id, envelope_id);
}
_ => panic!("expected External variant"),
}
}
#[tokio::test]
async fn test_inbox_try_drain() {
let (mut inbox, sender) = Inbox::new();
for i in 0..3 {
let mut envelope = make_test_envelope();
envelope.id = Uuid::from_u128(i as u128);
sender.send(InboxItem::External { envelope }).unwrap();
}
tokio::task::yield_now().await;
let items = inbox.try_drain();
assert_eq!(items.len(), 3);
for (i, item) in items.into_iter().enumerate() {
match item {
InboxItem::External { envelope } => {
assert_eq!(envelope.id.as_u128(), i as u128);
}
_ => panic!("expected External variant"),
}
}
let items = inbox.try_drain();
assert!(items.is_empty());
}
#[test]
fn test_sender_error_on_closed_inbox() {
let (inbox, sender) = Inbox::new();
drop(inbox);
let result = sender.send(InboxItem::External {
envelope: make_test_envelope(),
});
assert!(result.is_err());
}
#[test]
fn test_inbox_has_notify() {
let (inbox, _sender) = Inbox::new();
let notify = inbox.notify();
drop(notify);
}
#[tokio::test]
async fn test_sender_notifies_on_send() {
let (inbox, sender) = Inbox::new();
let notify = inbox.notify();
let notified = notify.notified();
sender
.send(InboxItem::External {
envelope: make_test_envelope(),
})
.unwrap();
let result = tokio::time::timeout(std::time::Duration::from_millis(100), notified).await;
assert!(result.is_ok(), "Should have been notified after send");
}
#[tokio::test]
async fn test_notify_wakes_waiting_task() {
let (inbox, sender) = Inbox::new();
let notify = inbox.notify();
let handle = tokio::spawn(async move {
notify.notified().await;
"woken"
});
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
sender
.send(InboxItem::External {
envelope: make_test_envelope(),
})
.unwrap();
let result = tokio::time::timeout(std::time::Duration::from_millis(100), handle).await;
assert!(result.is_ok(), "Task should have completed");
assert_eq!(result.unwrap().unwrap(), "woken");
}
}