use std::sync::Arc;
use bytes::Bytes;
use dashmap::DashMap;
use tokio::sync::Notify;
use crate::broker::fanout::{ConnectionSink, FanoutEngine, SubscribeIntent};
use crate::redis::connection::RedisPool;
struct TopicState {
count: usize,
}
pub struct FanoutBridge {
pool: RedisPool,
fanout: FanoutEngine,
topics: DashMap<String, TopicState>,
shutdown: Arc<Notify>,
}
impl FanoutBridge {
pub fn new(pool: RedisPool) -> Arc<Self> {
let shutdown = Arc::new(Notify::new());
let bridge = Arc::new(Self {
pool,
fanout: FanoutEngine::new(),
topics: DashMap::new(),
shutdown: shutdown.clone(),
});
let bridge_clone = bridge.clone();
tokio::spawn(async move {
bridge_clone.listen_loop().await;
});
bridge
}
pub fn fanout(&self) -> &FanoutEngine {
&self.fanout
}
pub fn ensure_topic(&self, topic: &str) {
let mut entry = self
.topics
.entry(topic.to_string())
.or_insert(TopicState { count: 0 });
entry.count += 1;
}
pub fn drop_topic(&self, topic: &str) {
self.topics.remove(topic);
}
fn deliver_to_local(&self, topic: &str, payload: Bytes) {
let framed = crate::broker::broker::serialize_frame_for_fanout(
&crate::frame::Frame {
payload: Some(payload),
..crate::frame::Frame::default()
},
0, );
self.fanout.deliver(topic, framed);
}
pub fn subscribe(
&self,
topic: &str,
intent: SubscribeIntent,
sink: ConnectionSink,
) -> crate::broker::fanout::SubscriptionId {
self.ensure_topic(topic);
self.fanout.subscribe(topic, intent, sink)
}
pub fn unsubscribe(&self, id: crate::broker::fanout::SubscriptionId) -> Option<String> {
self.fanout.unsubscribe(id)
}
pub fn drop_sink(&self, sink_id: u64) -> Vec<String> {
self.fanout.drop_sink(sink_id)
}
pub fn topic_subscriber_count(&self, topic: &str) -> usize {
self.fanout.topic_subscriber_count(topic)
}
async fn listen_loop(&self) {
use futures_util::StreamExt;
let prefix = self.pool.prefix().to_string();
let channel_pattern = format!("{prefix}:fanout:*");
let client = match redis::Client::open(self.pool.url()) {
Ok(c) => c,
Err(_) => return,
};
let mut pubsub = match client.get_async_pubsub().await {
Ok(ps) => ps,
Err(_) => return,
};
if pubsub.psubscribe(&channel_pattern).await.is_err() {
return;
}
let mut stream = pubsub.into_on_message();
loop {
tokio::select! {
_ = self.shutdown.notified() => {
return;
}
msg = stream.next() => {
let Some(msg) = msg else {
break;
};
let payload: Bytes = Bytes::from(msg.get_payload_bytes().to_vec());
let channel: String = msg.get_channel_name().into();
let topic = channel
.strip_prefix(&format!("{}:fanout:", prefix))
.unwrap_or(&channel);
self.deliver_to_local(topic, payload);
}
}
}
}
}