use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::broadcast;
use crate::realtime::error::ChannelError;
#[derive(Debug, Clone)]
pub struct ChannelPayload(Arc<[u8]>);
impl ChannelPayload {
#[must_use]
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self(bytes.into())
}
#[must_use]
pub fn from_static(bytes: &'static [u8]) -> Self {
Self(Arc::from(bytes))
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
struct Shared {
tx: broadcast::Sender<ChannelPayload>,
capacity: usize,
subscribers: Arc<AtomicUsize>,
}
pub struct Broadcast {
shared: Arc<Shared>,
}
impl Broadcast {
#[must_use]
pub fn new(capacity: usize) -> Option<Self> {
if capacity == 0 {
return None;
}
let (tx, _rx) = broadcast::channel(capacity);
Some(Self {
shared: Arc::new(Shared {
tx,
capacity,
subscribers: Arc::new(AtomicUsize::new(0)),
}),
})
}
#[must_use]
pub fn capacity(&self) -> usize {
self.shared.capacity
}
#[must_use]
pub fn subscriber_count(&self) -> usize {
self.shared.subscribers.load(Ordering::Relaxed)
}
pub fn publish(&self, payload: ChannelPayload) -> Result<usize, ChannelError> {
if self.shared.tx.receiver_count() == 0 {
return Ok(0);
}
match self.shared.tx.send(payload) {
Ok(n) => Ok(n),
Err(broadcast::error::SendError(_)) => Err(ChannelError::Closed),
}
}
#[must_use]
pub fn subscribe(&self) -> Subscription {
let rx = self.shared.tx.subscribe();
self.shared.subscribers.fetch_add(1, Ordering::Relaxed);
Subscription {
rx,
subscribers: Arc::clone(&self.shared.subscribers),
}
}
}
impl Clone for Broadcast {
fn clone(&self) -> Self {
Self {
shared: Arc::clone(&self.shared),
}
}
}
impl std::fmt::Debug for Broadcast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Broadcast")
.field("capacity", &self.shared.capacity)
.field("subscribers", &self.subscriber_count())
.finish()
}
}
pub struct Subscription {
pub rx: broadcast::Receiver<ChannelPayload>,
subscribers: Arc<AtomicUsize>,
}
impl Subscription {
pub async fn recv(&mut self) -> Result<ChannelPayload, ChannelError> {
match self.rx.recv().await {
Ok(payload) => Ok(payload),
Err(broadcast::error::RecvError::Lagged(_)) => Err(ChannelError::Lagged),
Err(broadcast::error::RecvError::Closed) => Err(ChannelError::Closed),
}
}
}
impl Drop for Subscription {
fn drop(&mut self) {
loop {
let current = self.subscribers.load(Ordering::Relaxed);
if current == 0 {
break;
}
if self
.subscribers
.compare_exchange(current, current - 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn publish_delivers_to_live_subscriber() {
let bc = Broadcast::new(8).expect("positive capacity");
let mut sub = bc.subscribe();
assert_eq!(bc.subscriber_count(), 1);
let delivered = bc
.publish(ChannelPayload::from_static(b"hello"))
.expect("publish to live subscriber");
assert_eq!(delivered, 1);
let payload = sub.recv().await.expect("receive payload");
assert_eq!(payload.as_bytes(), b"hello");
}
#[tokio::test]
async fn lagged_subscriber_is_surfaced_not_silently_dropped() {
let bc = Broadcast::new(1).expect("positive capacity");
let mut slow = bc.subscribe();
let _ = bc.publish(ChannelPayload::from_static(b"first"));
let _ = bc.publish(ChannelPayload::from_static(b"second"));
let outcome = slow.recv().await;
assert!(matches!(outcome, Err(ChannelError::Lagged)), "{outcome:?}");
}
#[tokio::test]
async fn closed_channel_surfaces_after_all_senders_drop() {
let bc = Broadcast::new(4).expect("positive capacity");
let mut sub = bc.subscribe();
drop(bc);
let outcome = sub.recv().await;
assert!(matches!(outcome, Err(ChannelError::Closed)), "{outcome:?}");
}
#[tokio::test]
async fn dropping_subscription_decrements_shared_count() {
let bc = Broadcast::new(4).expect("positive capacity");
let bc2 = bc.clone();
{
let _sub = bc.subscribe();
assert_eq!(bc.subscriber_count(), 1);
assert_eq!(bc2.subscriber_count(), 1, "count is shared across clones");
}
assert_eq!(bc.subscriber_count(), 0);
assert_eq!(bc2.subscriber_count(), 0);
}
#[tokio::test]
async fn publish_with_no_subscribers_reports_zero_not_error() {
let bc = Broadcast::new(4).expect("positive capacity");
let delivered = bc
.publish(ChannelPayload::from_static(b"orphan"))
.expect("send with no receivers is not an error");
assert_eq!(delivered, 0);
}
#[test]
fn zero_capacity_does_not_panic() {
assert!(Broadcast::new(0).is_none());
assert!(Broadcast::new(1).is_some());
}
#[test]
fn payload_from_bytes_and_static_share() {
let a = ChannelPayload::from_bytes(vec![1, 2, 3]);
let b = ChannelPayload::from_static(&[1, 2, 3]);
assert_eq!(a.as_bytes(), b.as_bytes());
let a2 = a.clone();
assert_eq!(a2.as_bytes(), a.as_bytes());
}
#[test]
fn broadcast_is_send_sync_clone_for_appstate() {
fn assert_send_sync_clone<T: Send + Sync + Clone>() {}
assert_send_sync_clone::<Broadcast>();
}
}