use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::stream::Stream;
use tokio::sync::{broadcast, mpsc};
use tokio_stream::wrappers::BroadcastStream;
use tracing::warn;
struct SubscriptionHandle<T, C> {
command_tx: mpsc::Sender<C>,
resubscribe_source: broadcast::Receiver<T>,
}
pub(crate) struct Subscription<T, C> {
inner: BroadcastStream<T>,
handle: Arc<SubscriptionHandle<T, C>>,
}
impl<T, C> Subscription<T, C>
where
T: Clone + Send + 'static,
C: Send + 'static,
{
pub(crate) fn start<F, Fut>(broadcast_capacity: usize, command_capacity: usize, run: F) -> Self
where
F: FnOnce(broadcast::Sender<T>, mpsc::Receiver<C>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
{
let (broadcast_tx, broadcast_rx) = broadcast::channel(broadcast_capacity);
let (command_tx, command_rx) = mpsc::channel(command_capacity);
let resubscribe_source = broadcast_tx.subscribe();
tokio::spawn(run(broadcast_tx, command_rx));
let handle = Arc::new(SubscriptionHandle {
command_tx,
resubscribe_source,
});
Subscription {
inner: BroadcastStream::new(broadcast_rx),
handle,
}
}
pub(crate) fn resubscribe(&self) -> Self {
Subscription {
inner: BroadcastStream::new(self.handle.resubscribe_source.resubscribe()),
handle: Arc::clone(&self.handle),
}
}
pub(crate) async fn send(&self, command: C) {
let _ = self.handle.command_tx.send(command).await;
}
}
impl<T, C> Stream for Subscription<T, C>
where
T: Clone + Send + 'static,
{
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match Pin::new(&mut self.inner).poll_next(cx) {
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
Poll::Ready(Some(Err(e))) => {
warn!("Broadcast subscription lagged: {:?}", e);
cx.waker().wake_by_ref();
Poll::Pending
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}