use super::{ChannelError, IncomingMessage, MessageChannel};
use tokio::sync::{Mutex, broadcast};
#[derive(Debug, Clone)]
pub struct BroadcastChannel {
tx: broadcast::Sender<String>,
}
impl Default for BroadcastChannel {
fn default() -> Self {
Self::new(256)
}
}
#[derive(Debug)]
pub struct BroadcastReceiver {
rx: Mutex<broadcast::Receiver<String>>,
}
impl BroadcastChannel {
pub fn new(capacity: usize) -> Self {
let (tx, _rx) = broadcast::channel(capacity);
Self { tx }
}
pub fn subscribe(&self) -> BroadcastReceiver {
BroadcastReceiver {
rx: Mutex::new(self.tx.subscribe()),
}
}
}
impl BroadcastReceiver {
pub async fn recv(&self) -> Result<IncomingMessage, ChannelError> {
let mut rx = self.rx.lock().await;
loop {
match rx.recv().await {
Ok(message) => {
return Ok(IncomingMessage {
text: message,
reply_tx: None,
});
}
Err(broadcast::error::RecvError::Lagged(_)) => continue,
Err(broadcast::error::RecvError::Closed) => return Err(ChannelError::Closed),
}
}
}
}
#[async_trait::async_trait]
impl MessageChannel for BroadcastChannel {
async fn ask(&self, _message: &str) -> Result<String, ChannelError> {
Err(ChannelError::NotSupported(
"broadcast channel does not support ask".into(),
))
}
async fn notify(&self, message: &str) -> Result<(), ChannelError> {
let _ = self.tx.send(message.to_string());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn notify_reaches_all_subscribers() {
let channel = BroadcastChannel::new(16);
let sub_a = channel.subscribe();
let sub_b = channel.subscribe();
channel.notify("start").await.unwrap();
let (a, b) = tokio::join!(sub_a.recv(), sub_b.recv());
let msg_a = a.unwrap();
let msg_b = b.unwrap();
assert_eq!(msg_a.text(), "start");
assert_eq!(msg_b.text(), "start");
assert!(!msg_a.wants_reply());
}
#[tokio::test]
async fn ask_not_supported() {
let channel = BroadcastChannel::new(16);
assert!(matches!(
channel.ask("q").await,
Err(ChannelError::NotSupported(_))
));
}
#[tokio::test]
async fn notify_without_subscribers_succeeds() {
let channel = BroadcastChannel::new(16);
channel.notify("n").await.unwrap();
let sub = channel.subscribe();
channel.notify("next").await.unwrap();
assert_eq!(sub.recv().await.unwrap().text(), "next");
}
}