Skip to main content

thruput_testkit/
mailbox.rs

1//! Off-chain messaging between agents.
2//!
3//! Agents communicate only through harness-provided channels, never raw I/O,
4//! so the harness stays in a position to observe their interactions. The
5//! [`Mailbox`] transport carries opaque bytes; the typed
6//! layer lives on `AgentContext::send`/`recv`, which CXS-encode and decode the
7//! payload and hand back a [`Message`].
8
9use std::collections::{HashMap, VecDeque};
10use std::sync::Arc;
11
12use tokio::sync::mpsc;
13use xdrs::ReadXDR;
14
15use crate::error::{Result, TestkitError};
16
17/// A raw message delivered to an agent's mailbox.
18#[derive(Debug, Clone)]
19pub struct Envelope {
20    /// Name of the agent that sent the message.
21    pub from: String,
22    /// Opaque message payload.
23    pub bytes: Vec<u8>,
24}
25
26impl Envelope {
27    /// Decode this envelope's CXS payload as `T`, keeping the sender's name.
28    pub(crate) fn decode<T>(self) -> Result<Message<T>>
29    where
30        T: for<'r> ReadXDR<'r>,
31    {
32        let body = xdrs::cxs::decode::<T>(&self.bytes).map_err(TestkitError::decode)?;
33        Ok(Message {
34            from: self.from,
35            body,
36        })
37    }
38}
39
40/// A received message with its CXS payload decoded to `T`.
41#[derive(Debug, Clone)]
42pub struct Message<T> {
43    /// Name of the agent that sent the message.
44    pub from: String,
45    /// The decoded message payload.
46    pub body: T,
47}
48
49/// An agent's inbound queue plus send handles to its peers.
50///
51/// `cache` holds messages already pulled off the channel but not yet withdrawn
52/// — set aside by a sender-selective [`recv_from`](Mailbox::recv_from) that
53/// skipped past them. They are the oldest messages, so plain
54/// [`recv`](Mailbox::recv) drains the cache before the channel.
55#[derive(Debug)]
56pub struct Mailbox {
57    name: Arc<str>,
58    inbox: mpsc::UnboundedReceiver<Envelope>,
59    peers: HashMap<String, mpsc::UnboundedSender<Envelope>>,
60    cache: VecDeque<Envelope>,
61}
62
63impl Mailbox {
64    pub(crate) fn new(
65        name: Arc<str>,
66        inbox: mpsc::UnboundedReceiver<Envelope>,
67        peers: HashMap<String, mpsc::UnboundedSender<Envelope>>,
68    ) -> Self {
69        Self {
70            name,
71            inbox,
72            peers,
73            cache: VecDeque::new(),
74        }
75    }
76
77    /// Send `bytes` to the agent named `to`.
78    ///
79    /// Fails with [`TestkitError::UnknownAgent`] when no such agent exists, or
80    /// [`TestkitError::MailboxClosed`] when the recipient has already finished.
81    pub fn send_to(&self, to: &str, bytes: impl Into<Vec<u8>>) -> Result<()> {
82        let sender = self
83            .peers
84            .get(to)
85            .ok_or_else(|| TestkitError::UnknownAgent {
86                name: to.to_owned(),
87            })?;
88        sender
89            .send(Envelope {
90                from: self.name.to_string(),
91                bytes: bytes.into(),
92            })
93            .map_err(|_| TestkitError::MailboxClosed {
94                name: to.to_owned(),
95            })
96    }
97
98    /// Wait for the next message from any sender, or `None` once every peer has
99    /// finished. Cached messages (set aside by [`recv_from`](Self::recv_from))
100    /// come first, oldest to newest, before anything still on the channel.
101    pub async fn recv(&mut self) -> Option<Envelope> {
102        if let Some(envelope) = self.cache.pop_front() {
103            return Some(envelope);
104        }
105        self.inbox.recv().await
106    }
107
108    /// Take a pending message from any sender without waiting.
109    pub fn try_recv(&mut self) -> Option<Envelope> {
110        if let Some(envelope) = self.cache.pop_front() {
111            return Some(envelope);
112        }
113        self.inbox.try_recv().ok()
114    }
115
116    /// Wait for the next message from any of the agents named in `from`, or
117    /// `None` once every peer has finished without one arriving.
118    ///
119    /// Messages from senders outside the set that arrive first are cached in
120    /// order and remain available to later [`recv`](Self::recv) / `recv_from*`
121    /// calls, so selecting by sender does not drop anyone else's messages.
122    pub async fn recv_from_any(&mut self, from: &[&str]) -> Option<Envelope> {
123        if let Some(index) = self.cache.iter().position(|e| sender_in(from, e)) {
124            return self.cache.remove(index);
125        }
126        loop {
127            match self.inbox.recv().await {
128                Some(envelope) if sender_in(from, &envelope) => return Some(envelope),
129                Some(envelope) => self.cache.push_back(envelope),
130                None => return None,
131            }
132        }
133    }
134
135    /// Take a pending message from any of the agents named in `from` without
136    /// waiting. Non-matching messages already waiting are cached, not dropped.
137    pub fn try_recv_from_any(&mut self, from: &[&str]) -> Option<Envelope> {
138        if let Some(index) = self.cache.iter().position(|e| sender_in(from, e)) {
139            return self.cache.remove(index);
140        }
141        loop {
142            match self.inbox.try_recv() {
143                Ok(envelope) if sender_in(from, &envelope) => return Some(envelope),
144                Ok(envelope) => self.cache.push_back(envelope),
145                Err(_) => return None,
146            }
147        }
148    }
149
150    /// Wait for the next message from the agent named `from`, or `None` once
151    /// every peer has finished without one arriving.
152    pub async fn recv_from(&mut self, from: &str) -> Option<Envelope> {
153        self.recv_from_any(&[from]).await
154    }
155
156    /// Take a pending message from the agent named `from` without waiting.
157    pub fn try_recv_from(&mut self, from: &str) -> Option<Envelope> {
158        self.try_recv_from_any(&[from])
159    }
160}
161
162/// Whether an envelope's sender is one of `senders`.
163fn sender_in(senders: &[&str], envelope: &Envelope) -> bool {
164    senders.iter().any(|s| envelope.from == *s)
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    fn envelope(from: &str, byte: u8) -> Envelope {
172        Envelope {
173            from: from.to_owned(),
174            bytes: vec![byte],
175        }
176    }
177
178    #[test]
179    fn recv_from_selects_a_sender_and_caches_the_rest() {
180        let (tx, rx) = mpsc::unbounded_channel();
181        let mut mailbox = Mailbox::new(Arc::from("bob"), rx, HashMap::new());
182
183        // Alice's message arrives first, then carol's.
184        tx.send(envelope("alice", 1)).expect("send alice");
185        tx.send(envelope("carol", 2)).expect("send carol");
186
187        // Selecting carol skips past alice's message without dropping it.
188        let carol = mailbox.try_recv_from("carol").expect("carol's message");
189        assert_eq!(carol.from, "carol");
190        assert_eq!(carol.bytes, vec![2]);
191
192        // Alice's cached message is the next one a plain recv sees.
193        let alice = mailbox.try_recv().expect("alice's cached message");
194        assert_eq!(alice.from, "alice");
195        assert_eq!(alice.bytes, vec![1]);
196
197        // Nothing left.
198        assert!(mailbox.try_recv().is_none());
199    }
200
201    #[test]
202    fn recv_from_returns_none_when_no_matching_sender_remains() {
203        let (tx, rx) = mpsc::unbounded_channel();
204        let mut mailbox = Mailbox::new(Arc::from("bob"), rx, HashMap::new());
205
206        tx.send(envelope("alice", 1)).expect("send alice");
207
208        // No message from carol is pending; alice's is cached, not consumed.
209        assert!(mailbox.try_recv_from("carol").is_none());
210        let alice = mailbox.try_recv().expect("alice's cached message");
211        assert_eq!(alice.from, "alice");
212    }
213
214    #[test]
215    fn recv_from_any_accepts_any_member_and_caches_outsiders() {
216        let (tx, rx) = mpsc::unbounded_channel();
217        let mut mailbox = Mailbox::new(Arc::from("admin"), rx, HashMap::new());
218
219        // A stranger's message arrives before the two providers'.
220        tx.send(envelope("stranger", 9)).expect("send stranger");
221        tx.send(envelope("bob", 2)).expect("send bob");
222        tx.send(envelope("alice", 1)).expect("send alice");
223
224        // Selecting the provider set skips the stranger and yields providers in
225        // arrival order.
226        let first = mailbox
227            .try_recv_from_any(&["alice", "bob"])
228            .expect("first provider");
229        assert_eq!(first.from, "bob");
230        let second = mailbox
231            .try_recv_from_any(&["alice", "bob"])
232            .expect("second provider");
233        assert_eq!(second.from, "alice");
234
235        // The stranger's message was cached, not dropped.
236        let stranger = mailbox.try_recv().expect("stranger's cached message");
237        assert_eq!(stranger.from, "stranger");
238    }
239}