use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use hydracache_core::{CacheCodec, PostcardCodec};
use moka::future::Cache;
use tokio::sync::watch;
use crate::cache::{HydraCache, HydraCacheInner};
use crate::cluster::ClusterRuntime;
use crate::entry::CacheEntry;
use crate::events::EventBus;
use crate::inflight::InFlightMap;
use crate::invalidation_bus::CacheInvalidationBus;
use crate::stats::StatsCounters;
use crate::tag_index::TagIndex;
static NEXT_INVALIDATION_NODE_ID: AtomicU64 = AtomicU64::new(1);
fn next_invalidation_node_id() -> String {
let id = NEXT_INVALIDATION_NODE_ID.fetch_add(1, Ordering::Relaxed);
format!("hydracache-node-{id}")
}
#[derive(Debug, Clone)]
pub struct HydraCacheBuilder<C = PostcardCodec>
where
C: CacheCodec,
{
max_capacity: u64,
max_entry_bytes: usize,
default_ttl: Duration,
event_buffer_capacity: usize,
access_events: bool,
invalidation_bus: Option<Arc<dyn CacheInvalidationBus>>,
invalidation_node_id: Option<String>,
cluster_runtime: Option<ClusterRuntime>,
codec: C,
}
impl<C> HydraCacheBuilder<C>
where
C: CacheCodec,
{
pub fn max_capacity(mut self, max_capacity: u64) -> Self {
self.max_capacity = max_capacity.max(1);
self
}
pub fn max_entry_bytes(mut self, max_entry_bytes: usize) -> Self {
self.max_entry_bytes = max_entry_bytes.max(1);
self
}
pub fn default_ttl(mut self, default_ttl: Duration) -> Self {
self.default_ttl = default_ttl;
self
}
pub fn event_buffer_capacity(mut self, capacity: usize) -> Self {
self.event_buffer_capacity = capacity.max(1);
self
}
pub fn enable_access_events(mut self, enabled: bool) -> Self {
self.access_events = enabled;
self
}
pub fn shared_invalidation_bus(mut self, bus: Arc<dyn CacheInvalidationBus>) -> Self {
self.invalidation_bus = Some(bus);
self
}
pub fn invalidation_bus<B>(self, bus: B) -> Self
where
B: CacheInvalidationBus,
{
self.shared_invalidation_bus(Arc::new(bus))
}
pub fn invalidation_node_id(mut self, node_id: impl Into<String>) -> Self {
self.invalidation_node_id = Some(node_id.into());
self
}
pub fn codec<Next>(self, codec: Next) -> HydraCacheBuilder<Next>
where
Next: CacheCodec,
{
HydraCacheBuilder {
max_capacity: self.max_capacity,
max_entry_bytes: self.max_entry_bytes,
default_ttl: self.default_ttl,
event_buffer_capacity: self.event_buffer_capacity,
access_events: self.access_events,
invalidation_bus: self.invalidation_bus,
invalidation_node_id: self.invalidation_node_id,
cluster_runtime: self.cluster_runtime,
codec,
}
}
pub(crate) fn cluster_runtime(mut self, runtime: ClusterRuntime) -> Self {
self.cluster_runtime = Some(runtime);
self
}
pub fn build(self) -> HydraCache<C> {
let max_entry_bytes = self.max_entry_bytes;
let store = Cache::builder()
.max_capacity(self.max_capacity)
.weigher(move |_key, entry: &CacheEntry| {
entry.value.len().min(max_entry_bytes).max(1) as u32
})
.build();
let invalidation_node_id = self
.invalidation_node_id
.unwrap_or_else(next_invalidation_node_id);
let (bus_shutdown, bus_shutdown_rx) = self
.invalidation_bus
.as_ref()
.map(|_| watch::channel(false))
.map_or((None, None), |(sender, receiver)| {
(Some(sender), Some(receiver))
});
let cache = HydraCache {
inner: Arc::new(HydraCacheInner {
store,
tag_index: TagIndex::default(),
in_flight: InFlightMap::default(),
codec: self.codec,
default_ttl: self.default_ttl,
max_entry_bytes,
stats: Arc::new(StatsCounters::default()),
events: EventBus::new(self.event_buffer_capacity, self.access_events),
invalidation_bus: self.invalidation_bus,
invalidation_node_id,
bus_shutdown,
cluster_runtime: self.cluster_runtime,
}),
};
if let Some(shutdown) = bus_shutdown_rx {
cache.spawn_invalidation_listener(shutdown);
}
cache
}
}
impl Default for HydraCacheBuilder<PostcardCodec> {
fn default() -> Self {
Self {
max_capacity: 10_000,
max_entry_bytes: 16 * 1024 * 1024,
default_ttl: Duration::from_secs(300),
event_buffer_capacity: 1024,
access_events: false,
invalidation_bus: None,
invalidation_node_id: None,
cluster_runtime: None,
codec: PostcardCodec,
}
}
}