use super::{ChannelError, Envelope, IncomingMessage, MessageChannel};
use tokio::sync::{Mutex, mpsc, oneshot};
#[derive(Debug)]
pub struct MpscChannel {
tx: mpsc::Sender<Envelope>,
rx: Mutex<mpsc::Receiver<Envelope>>,
}
const DEFAULT_CAPACITY: usize = 64;
impl MpscChannel {
pub fn pair() -> (MpscChannel, MpscChannel) {
Self::pair_with_capacity(DEFAULT_CAPACITY)
}
pub fn pair_with_capacity(capacity: usize) -> (MpscChannel, MpscChannel) {
let (tx_a, rx_a) = mpsc::channel(capacity);
let (tx_b, rx_b) = mpsc::channel(capacity);
(
MpscChannel {
tx: tx_a,
rx: Mutex::new(rx_b),
},
MpscChannel {
tx: tx_b,
rx: Mutex::new(rx_a),
},
)
}
pub async fn recv(&self) -> Result<IncomingMessage, ChannelError> {
let mut rx = self.rx.lock().await;
let envelope = rx.recv().await.ok_or(ChannelError::Closed)?;
Ok(IncomingMessage {
text: envelope.message,
reply_tx: envelope.reply,
})
}
}
#[async_trait::async_trait]
impl MessageChannel for MpscChannel {
async fn ask(&self, message: &str) -> Result<String, ChannelError> {
let (reply_tx, reply_rx) = oneshot::channel();
self.tx
.send(Envelope {
message: message.to_string(),
reply: Some(reply_tx),
})
.await
.map_err(|_| ChannelError::Closed)?;
reply_rx.await.map_err(|_| ChannelError::Closed)
}
async fn notify(&self, message: &str) -> Result<(), ChannelError> {
self.tx
.send(Envelope {
message: message.to_string(),
reply: None,
})
.await
.map_err(|_| ChannelError::Closed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn ask_reply_bound() {
let (a, b) = MpscChannel::pair();
let ask = a.ask("q");
let b_side = async {
let incoming = b.recv().await.unwrap();
assert_eq!(incoming.text(), "q");
assert!(incoming.wants_reply());
incoming.reply("ans".into())
};
let (answer, _) = tokio::join!(ask, b_side);
assert_eq!(answer.unwrap(), "ans");
}
#[tokio::test]
async fn concurrent_bidirectional_ask_replies_stay_bound() {
let (a, b) = MpscChannel::pair();
let a_ask = a.ask("q_from_a");
let b_ask = b.ask("q_from_b");
let a_side = async {
let incoming = a.recv().await.unwrap();
assert_eq!(incoming.text(), "q_from_b");
incoming.reply("ans_to_b".into())
};
let b_side = async {
let incoming = b.recv().await.unwrap();
assert_eq!(incoming.text(), "q_from_a");
incoming.reply("ans_to_a".into())
};
let (r_a, r_b, _a_side, _b_side) = tokio::join!(a_ask, b_ask, a_side, b_side);
assert_eq!(r_a.unwrap(), "ans_to_a");
assert_eq!(r_b.unwrap(), "ans_to_b");
}
#[tokio::test]
async fn notify_has_no_reply() {
let (a, b) = MpscChannel::pair();
let notify = a.notify("n");
let b_side = async {
let incoming = b.recv().await.unwrap();
assert_eq!(incoming.text(), "n");
assert!(!incoming.wants_reply());
incoming.reply("x".into())
};
let (n_res, b_res) = tokio::join!(notify, b_side);
n_res.unwrap();
assert!(matches!(b_res.unwrap_err(), ChannelError::NoReply));
}
#[tokio::test]
async fn ask_when_peer_dropped_returns_closed() {
let (a, _b) = MpscChannel::pair();
drop(_b); assert!(matches!(a.ask("q").await, Err(ChannelError::Closed)));
}
}