use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use hydracache_core::{CacheCodec, CacheError, PostcardCodec, Result};
use moka::future::Cache;
use tokio::sync::watch;
use crate::cache::{HydraCache, HydraCacheInner};
use crate::cluster::{ClusterRuntime, RoutingMode, TransportPosture};
use crate::entry::CacheEntry;
use crate::events::EventBus;
use crate::grid::persistence_policy::PersistencePolicy;
use crate::grid::{ReplicatedValueSecurityPosture, ReplicationConfig};
use crate::inflight::InFlightMap;
use crate::invalidation_bus::CacheInvalidationBus;
use crate::load_breaker::{LoadBreakerPolicy, LoadBreakerRegistry};
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>,
transport_posture: TransportPosture,
routing_mode: RoutingMode,
read_through_enabled: bool,
replication_config: ReplicationConfig,
replicated_value_security: ReplicatedValueSecurityPosture,
persistence_policy: Option<PersistencePolicy>,
persistence_storage_dir: Option<PathBuf>,
load_breaker_policy: LoadBreakerPolicy,
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,
transport_posture: self.transport_posture,
routing_mode: self.routing_mode,
read_through_enabled: self.read_through_enabled,
replication_config: self.replication_config,
replicated_value_security: self.replicated_value_security,
persistence_policy: self.persistence_policy,
persistence_storage_dir: self.persistence_storage_dir,
load_breaker_policy: self.load_breaker_policy,
codec,
}
}
pub(crate) fn cluster_runtime(mut self, runtime: ClusterRuntime) -> Self {
self.cluster_runtime = Some(runtime);
self
}
pub fn transport_auth_configured(mut self, enabled: bool) -> Self {
self.transport_posture.auth = enabled;
self
}
pub fn strict_wire_compatibility(mut self, enabled: bool) -> Self {
self.transport_posture.wire_strict = enabled;
self
}
pub fn declare_mesh_boundary(mut self, enabled: bool) -> Self {
self.transport_posture.mesh_declared = enabled;
self
}
pub fn routing_mode(mut self, routing_mode: RoutingMode) -> Self {
self.routing_mode = routing_mode;
self
}
pub fn read_through_enabled(mut self, enabled: bool) -> Self {
self.read_through_enabled = enabled;
self
}
pub fn replicate_values(mut self, enabled: bool) -> Self {
self.replication_config.replicate_values = enabled;
self.replicated_value_security = if enabled {
ReplicatedValueSecurityPosture::PlaintextUnacknowledged
} else {
ReplicatedValueSecurityPosture::Disabled
};
self
}
pub fn replication_factor(mut self, replication_factor: usize) -> Self {
self.replication_config.replication_factor = replication_factor.max(1);
self
}
pub fn read_quorum(mut self, read_quorum: usize) -> Self {
self.replication_config.read_quorum = read_quorum.max(1);
self
}
pub fn write_quorum(mut self, write_quorum: usize) -> Self {
self.replication_config.write_quorum = write_quorum.max(1);
self
}
pub fn sync_backups(mut self, sync_backups: usize) -> Self {
self.replication_config.sync_backups = sync_backups;
self
}
pub fn async_backups(mut self, async_backups: usize) -> Self {
self.replication_config.async_backups = async_backups;
self
}
pub fn max_replicated_entry_bytes(mut self, max_replicated_entry_bytes: usize) -> Self {
self.replication_config.max_replicated_entry_bytes = max_replicated_entry_bytes.max(1);
self
}
pub fn acknowledge_plaintext_replicated_values(mut self, acknowledged: bool) -> Self {
if self.replication_config.replicate_values && acknowledged {
self.replicated_value_security = ReplicatedValueSecurityPosture::PlaintextAcknowledged;
} else if self.replication_config.replicate_values {
self.replicated_value_security =
ReplicatedValueSecurityPosture::PlaintextUnacknowledged;
}
self
}
pub fn replicated_values_encrypted(mut self, encrypted: bool) -> Self {
if self.replication_config.replicate_values && encrypted {
self.replicated_value_security = ReplicatedValueSecurityPosture::Encrypted;
}
self
}
pub(crate) fn validate_replication_config(&self) -> Result<()> {
self.replication_config
.validate()
.map_err(|error| CacheError::Backend(format!("invalid replication config: {error}")))
}
pub fn with_persistence_policy(mut self, policy: PersistencePolicy) -> Self {
self.persistence_policy = Some(policy);
self
}
pub fn with_storage_dir(mut self, storage_dir: impl Into<PathBuf>) -> Self {
self.persistence_storage_dir = Some(storage_dir.into());
self
}
pub fn load_breaker(
mut self,
failure_threshold: u32,
initial_backoff: Duration,
max_backoff: Duration,
) -> Self {
self.load_breaker_policy =
LoadBreakerPolicy::new(failure_threshold, initial_backoff, max_backoff);
self
}
pub fn load_breaker_policy(mut self, policy: LoadBreakerPolicy) -> Self {
self.load_breaker_policy = policy;
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,
consistency_generation: AtomicU64::new(0),
bus_shutdown,
cluster_runtime: self.cluster_runtime,
transport_posture: self.transport_posture,
routing_mode: self.routing_mode,
read_through_enabled: self.read_through_enabled,
replication_config: self.replication_config,
replicated_value_security: self.replicated_value_security,
load_breaker: LoadBreakerRegistry::new(self.load_breaker_policy),
}),
};
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,
transport_posture: TransportPosture::default(),
routing_mode: RoutingMode::default(),
read_through_enabled: true,
replication_config: ReplicationConfig::default(),
replicated_value_security: ReplicatedValueSecurityPosture::Disabled,
persistence_policy: None,
persistence_storage_dir: None,
load_breaker_policy: LoadBreakerPolicy::default(),
codec: PostcardCodec,
}
}
}