use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::sync::mpsc;
use xdrs::ReadXDR;
use crate::error::{Result, TestkitError};
#[derive(Debug, Clone)]
pub struct Envelope {
pub from: String,
pub bytes: Vec<u8>,
}
impl Envelope {
pub(crate) fn decode<T>(self) -> Result<Message<T>>
where
T: for<'r> ReadXDR<'r>,
{
let body = xdrs::cxs::decode::<T>(&self.bytes).map_err(TestkitError::decode)?;
Ok(Message {
from: self.from,
body,
})
}
}
#[derive(Debug, Clone)]
pub struct Message<T> {
pub from: String,
pub body: T,
}
#[derive(Debug)]
pub struct Mailbox {
name: Arc<str>,
inbox: mpsc::UnboundedReceiver<Envelope>,
peers: HashMap<String, mpsc::UnboundedSender<Envelope>>,
cache: VecDeque<Envelope>,
}
impl Mailbox {
pub(crate) fn new(
name: Arc<str>,
inbox: mpsc::UnboundedReceiver<Envelope>,
peers: HashMap<String, mpsc::UnboundedSender<Envelope>>,
) -> Self {
Self {
name,
inbox,
peers,
cache: VecDeque::new(),
}
}
pub fn send_to(&self, to: &str, bytes: impl Into<Vec<u8>>) -> Result<()> {
let sender = self
.peers
.get(to)
.ok_or_else(|| TestkitError::UnknownAgent {
name: to.to_owned(),
})?;
sender
.send(Envelope {
from: self.name.to_string(),
bytes: bytes.into(),
})
.map_err(|_| TestkitError::MailboxClosed {
name: to.to_owned(),
})
}
pub async fn recv(&mut self) -> Option<Envelope> {
if let Some(envelope) = self.cache.pop_front() {
return Some(envelope);
}
self.inbox.recv().await
}
pub fn try_recv(&mut self) -> Option<Envelope> {
if let Some(envelope) = self.cache.pop_front() {
return Some(envelope);
}
self.inbox.try_recv().ok()
}
pub async fn recv_from_any(&mut self, from: &[&str]) -> Option<Envelope> {
if let Some(index) = self.cache.iter().position(|e| sender_in(from, e)) {
return self.cache.remove(index);
}
loop {
match self.inbox.recv().await {
Some(envelope) if sender_in(from, &envelope) => return Some(envelope),
Some(envelope) => self.cache.push_back(envelope),
None => return None,
}
}
}
pub fn try_recv_from_any(&mut self, from: &[&str]) -> Option<Envelope> {
if let Some(index) = self.cache.iter().position(|e| sender_in(from, e)) {
return self.cache.remove(index);
}
loop {
match self.inbox.try_recv() {
Ok(envelope) if sender_in(from, &envelope) => return Some(envelope),
Ok(envelope) => self.cache.push_back(envelope),
Err(_) => return None,
}
}
}
pub async fn recv_from(&mut self, from: &str) -> Option<Envelope> {
self.recv_from_any(&[from]).await
}
pub fn try_recv_from(&mut self, from: &str) -> Option<Envelope> {
self.try_recv_from_any(&[from])
}
}
fn sender_in(senders: &[&str], envelope: &Envelope) -> bool {
senders.iter().any(|s| envelope.from == *s)
}
#[cfg(test)]
mod tests {
use super::*;
fn envelope(from: &str, byte: u8) -> Envelope {
Envelope {
from: from.to_owned(),
bytes: vec![byte],
}
}
#[test]
fn recv_from_selects_a_sender_and_caches_the_rest() {
let (tx, rx) = mpsc::unbounded_channel();
let mut mailbox = Mailbox::new(Arc::from("bob"), rx, HashMap::new());
tx.send(envelope("alice", 1)).expect("send alice");
tx.send(envelope("carol", 2)).expect("send carol");
let carol = mailbox.try_recv_from("carol").expect("carol's message");
assert_eq!(carol.from, "carol");
assert_eq!(carol.bytes, vec![2]);
let alice = mailbox.try_recv().expect("alice's cached message");
assert_eq!(alice.from, "alice");
assert_eq!(alice.bytes, vec![1]);
assert!(mailbox.try_recv().is_none());
}
#[test]
fn recv_from_returns_none_when_no_matching_sender_remains() {
let (tx, rx) = mpsc::unbounded_channel();
let mut mailbox = Mailbox::new(Arc::from("bob"), rx, HashMap::new());
tx.send(envelope("alice", 1)).expect("send alice");
assert!(mailbox.try_recv_from("carol").is_none());
let alice = mailbox.try_recv().expect("alice's cached message");
assert_eq!(alice.from, "alice");
}
#[test]
fn recv_from_any_accepts_any_member_and_caches_outsiders() {
let (tx, rx) = mpsc::unbounded_channel();
let mut mailbox = Mailbox::new(Arc::from("admin"), rx, HashMap::new());
tx.send(envelope("stranger", 9)).expect("send stranger");
tx.send(envelope("bob", 2)).expect("send bob");
tx.send(envelope("alice", 1)).expect("send alice");
let first = mailbox
.try_recv_from_any(&["alice", "bob"])
.expect("first provider");
assert_eq!(first.from, "bob");
let second = mailbox
.try_recv_from_any(&["alice", "bob"])
.expect("second provider");
assert_eq!(second.from, "alice");
let stranger = mailbox.try_recv().expect("stranger's cached message");
assert_eq!(stranger.from, "stranger");
}
}