use std::fmt;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use hydracache_core::{CacheEvent, CacheEventKind, CacheEventOptions};
use tokio::sync::broadcast;
use tokio::task::JoinHandle;
use crate::stats::StatsCounters;
#[derive(Debug)]
pub(crate) struct EventBus {
sender: broadcast::Sender<CacheEvent>,
access_events: bool,
}
impl EventBus {
pub(crate) fn new(capacity: usize, access_events: bool) -> Self {
let (sender, _) = broadcast::channel(capacity.max(1));
Self {
sender,
access_events,
}
}
pub(crate) fn subscribe(
&self,
options: CacheEventOptions,
stats: Arc<StatsCounters>,
) -> CacheEventSubscriber {
CacheEventSubscriber {
receiver: self.sender.subscribe(),
options,
stats,
}
}
pub(crate) fn publish(&self, event: CacheEvent, stats: &StatsCounters) {
if !self.should_publish(event.kind()) || self.sender.receiver_count() == 0 {
return;
}
if self.sender.send(event).is_ok() {
stats.events_published.fetch_add(1, Ordering::Relaxed);
}
}
fn should_publish(&self, kind: CacheEventKind) -> bool {
self.access_events || kind.is_mutation()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CacheEventRecvError {
Closed,
Lagged(u64),
}
impl fmt::Display for CacheEventRecvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Closed => f.write_str("cache event subscription closed"),
Self::Lagged(skipped) => write!(f, "cache event subscriber lagged by {skipped} events"),
}
}
}
impl std::error::Error for CacheEventRecvError {}
#[derive(Debug)]
pub struct CacheEventSubscriber {
receiver: broadcast::Receiver<CacheEvent>,
options: CacheEventOptions,
stats: Arc<StatsCounters>,
}
impl CacheEventSubscriber {
pub async fn recv(&mut self) -> Result<CacheEvent, CacheEventRecvError> {
loop {
match self.receiver.recv().await {
Ok(event) if self.options.matches(&event) => return Ok(event),
Ok(_) => continue,
Err(broadcast::error::RecvError::Closed) => {
return Err(CacheEventRecvError::Closed);
}
Err(broadcast::error::RecvError::Lagged(skipped)) => {
self.stats
.event_subscriber_lagged
.fetch_add(skipped, Ordering::Relaxed);
return Err(CacheEventRecvError::Lagged(skipped));
}
}
}
}
pub async fn next_event(&mut self) -> Option<CacheEvent> {
loop {
match self.recv().await {
Ok(event) => return Some(event),
Err(CacheEventRecvError::Closed) => return None,
Err(CacheEventRecvError::Lagged(_)) => continue,
}
}
}
pub fn options(&self) -> &CacheEventOptions {
&self.options
}
}
pub struct CacheEventListenerHandle {
task: JoinHandle<()>,
}
impl CacheEventListenerHandle {
pub(crate) fn spawn<F>(mut subscriber: CacheEventSubscriber, listener: F) -> Self
where
F: Fn(CacheEvent) + Send + 'static,
{
let task = tokio::spawn(async move {
while let Some(event) = subscriber.next_event().await {
listener(event);
}
});
Self { task }
}
pub fn unsubscribe(self) {
self.task.abort();
}
pub fn is_finished(&self) -> bool {
self.task.is_finished()
}
}
impl Drop for CacheEventListenerHandle {
fn drop(&mut self) {
self.task.abort();
}
}