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.may_publish(event.kind()) {
return;
}
if self.sender.send(event).is_ok() {
stats.events_published.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) fn may_publish(&self, kind: CacheEventKind) -> bool {
self.should_publish(kind) && self.sender.receiver_count() > 0
}
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();
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use hydracache_core::{CacheEventKind, CacheEventOptions};
use super::EventBus;
use crate::stats::StatsCounters;
#[test]
fn preflight_requires_an_active_subscriber() {
let bus = EventBus::new(16, false);
assert!(!bus.may_publish(CacheEventKind::Stored));
let subscriber = bus.subscribe(
CacheEventOptions::mutations(),
Arc::new(StatsCounters::default()),
);
assert!(bus.may_publish(CacheEventKind::Stored));
drop(subscriber);
assert!(!bus.may_publish(CacheEventKind::Stored));
}
#[test]
fn preflight_keeps_access_events_disabled_until_enabled() {
let disabled_bus = EventBus::new(16, false);
let _disabled_subscriber = disabled_bus.subscribe(
CacheEventOptions::access(),
Arc::new(StatsCounters::default()),
);
assert!(!disabled_bus.may_publish(CacheEventKind::Hit));
assert!(disabled_bus.may_publish(CacheEventKind::Stored));
let enabled_bus = EventBus::new(16, true);
let _enabled_subscriber = enabled_bus.subscribe(
CacheEventOptions::access(),
Arc::new(StatsCounters::default()),
);
assert!(enabled_bus.may_publish(CacheEventKind::Hit));
assert!(enabled_bus.may_publish(CacheEventKind::Stored));
}
}