use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{self, AtomicUsize};
use std::sync::Arc;
pub use cdk_common::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
use cdk_common::pub_sub::OnNewSubscription;
pub use cdk_common::pub_sub::SubId;
use tokio::sync::{mpsc, RwLock};
use tokio::task::JoinHandle;
type IndexTree<T, I> = Arc<RwLock<BTreeMap<Index<I>, mpsc::Sender<(SubId, T)>>>>;
pub const DEFAULT_REMOVE_SIZE: usize = 10_000;
pub const DEFAULT_CHANNEL_SIZE: usize = 10;
pub struct Manager<T, I, F>
where
T: Indexable<Type = I> + Clone + Send + Sync + 'static,
I: PartialOrd + Clone + Debug + Ord + Send + Sync + 'static,
F: OnNewSubscription<Index = I, Event = T> + 'static,
{
indexes: IndexTree<T, I>,
on_new_subscription: Option<F>,
unsubscription_sender: mpsc::Sender<(SubId, Vec<Index<I>>)>,
active_subscriptions: Arc<AtomicUsize>,
background_subscription_remover: Option<JoinHandle<()>>,
}
impl<T, I, F> Default for Manager<T, I, F>
where
T: Indexable<Type = I> + Clone + Send + Sync + 'static,
I: PartialOrd + Clone + Debug + Ord + Send + Sync + 'static,
F: OnNewSubscription<Index = I, Event = T> + 'static,
{
fn default() -> Self {
let (sender, receiver) = mpsc::channel(DEFAULT_REMOVE_SIZE);
let active_subscriptions: Arc<AtomicUsize> = Default::default();
let storage: IndexTree<T, I> = Arc::new(Default::default());
Self {
background_subscription_remover: Some(tokio::spawn(Self::remove_subscription(
receiver,
storage.clone(),
active_subscriptions.clone(),
))),
on_new_subscription: None,
unsubscription_sender: sender,
active_subscriptions,
indexes: storage,
}
}
}
impl<T, I, F> From<F> for Manager<T, I, F>
where
T: Indexable<Type = I> + Clone + Send + Sync + 'static,
I: PartialOrd + Clone + Debug + Ord + Send + Sync + 'static,
F: OnNewSubscription<Index = I, Event = T> + 'static,
{
fn from(value: F) -> Self {
let mut manager: Self = Default::default();
manager.on_new_subscription = Some(value);
manager
}
}
impl<T, I, F> Manager<T, I, F>
where
T: Indexable<Type = I> + Clone + Send + Sync + 'static,
I: PartialOrd + Clone + Debug + Ord + Send + Sync + 'static,
F: OnNewSubscription<Index = I, Event = T> + 'static,
{
#[inline]
async fn broadcast_impl(storage: &IndexTree<T, I>, event: T) {
let index_storage = storage.read().await;
let mut sent = HashSet::new();
for index in event.to_indexes() {
for (key, sender) in index_storage.range(index.clone()..) {
if index.cmp_prefix(key) != Ordering::Equal {
break;
}
let sub_id = key.unique_id();
if sent.contains(&sub_id) {
continue;
}
sent.insert(sub_id);
let _ = sender.try_send((key.into(), event.clone()));
}
}
}
pub fn broadcast(&self, event: T) {
let storage = self.indexes.clone();
tokio::spawn(async move {
Self::broadcast_impl(&storage, event).await;
});
}
pub async fn broadcast_async(&self, event: T) {
Self::broadcast_impl(&self.indexes, event).await;
}
#[inline(always)]
async fn subscribe_inner(
&self,
sub_id: SubId,
indexes: Vec<Index<I>>,
) -> ActiveSubscription<T, I> {
let (sender, receiver) = mpsc::channel(10);
if let Some(on_new_subscription) = self.on_new_subscription.as_ref() {
match on_new_subscription
.on_new_subscription(&indexes.iter().map(|x| x.deref()).collect::<Vec<_>>())
.await
{
Ok(events) => {
for event in events {
let _ = sender.try_send((sub_id.clone(), event));
}
}
Err(err) => {
tracing::info!(
"Failed to get initial state for subscription: {:?}, {}",
sub_id,
err
);
}
}
}
let mut index_storage = self.indexes.write().await;
for index in indexes.clone() {
index_storage.insert(index, sender.clone());
}
drop(index_storage);
self.active_subscriptions
.fetch_add(1, atomic::Ordering::Relaxed);
ActiveSubscription {
sub_id,
receiver,
indexes,
drop: self.unsubscription_sender.clone(),
}
}
pub async fn try_subscribe<P: AsRef<SubId> + TryInto<Vec<Index<I>>>>(
&self,
params: P,
) -> Result<ActiveSubscription<T, I>, P::Error> {
Ok(self
.subscribe_inner(params.as_ref().clone(), params.try_into()?)
.await)
}
pub async fn subscribe<P: AsRef<SubId> + Into<Vec<Index<I>>>>(
&self,
params: P,
) -> ActiveSubscription<T, I> {
self.subscribe_inner(params.as_ref().clone(), params.into())
.await
}
pub fn active_subscriptions(&self) -> usize {
self.active_subscriptions.load(atomic::Ordering::SeqCst)
}
async fn remove_subscription(
mut receiver: mpsc::Receiver<(SubId, Vec<Index<I>>)>,
storage: IndexTree<T, I>,
active_subscriptions: Arc<AtomicUsize>,
) {
while let Some((sub_id, indexes)) = receiver.recv().await {
tracing::info!("Removing subscription: {}", *sub_id);
active_subscriptions.fetch_sub(1, atomic::Ordering::AcqRel);
let mut index_storage = storage.write().await;
for key in indexes {
index_storage.remove(&key);
}
drop(index_storage);
}
}
}
impl<T, I, F> Drop for Manager<T, I, F>
where
T: Indexable<Type = I> + Clone + Send + Sync + 'static,
I: Clone + Debug + PartialOrd + Ord + Send + Sync + 'static,
F: OnNewSubscription<Index = I, Event = T> + 'static,
{
fn drop(&mut self) {
if let Some(handler) = self.background_subscription_remover.take() {
handler.abort();
}
}
}
pub struct ActiveSubscription<T, I>
where
T: Send + Sync,
I: Clone + Debug + PartialOrd + Ord + Send + Sync + 'static,
{
pub sub_id: SubId,
indexes: Vec<Index<I>>,
receiver: mpsc::Receiver<(SubId, T)>,
drop: mpsc::Sender<(SubId, Vec<Index<I>>)>,
}
impl<T, I> Deref for ActiveSubscription<T, I>
where
T: Send + Sync,
I: Clone + Debug + PartialOrd + Ord + Send + Sync + 'static,
{
type Target = mpsc::Receiver<(SubId, T)>;
fn deref(&self) -> &Self::Target {
&self.receiver
}
}
impl<T, I> DerefMut for ActiveSubscription<T, I>
where
T: Indexable + Clone + Send + Sync + 'static,
I: Clone + Debug + PartialOrd + Ord + Send + Sync + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.receiver
}
}
impl<T, I> Drop for ActiveSubscription<T, I>
where
T: Send + Sync,
I: Clone + Debug + PartialOrd + Ord + Send + Sync + 'static,
{
fn drop(&mut self) {
let _ = self
.drop
.try_send((self.sub_id.clone(), self.indexes.drain(..).collect()));
}
}
#[cfg(test)]
mod test {
use tokio::sync::mpsc;
use super::*;
#[test]
fn test_active_subscription_drop() {
let (tx, rx) = mpsc::channel::<(SubId, ())>(10);
let sub_id = SubId::from("test_sub_id");
let indexes: Vec<Index<String>> = vec![Index::from(("test".to_string(), sub_id.clone()))];
let (drop_tx, mut drop_rx) = mpsc::channel(10);
{
let _active_subscription = ActiveSubscription {
sub_id: sub_id.clone(),
indexes,
receiver: rx,
drop: drop_tx,
};
}
assert_eq!(drop_rx.try_recv().unwrap().0, sub_id); assert!(tx.try_send(("foo".into(), ())).is_err()); }
}