use super::{Cache, SegmentedCache};
use crate::{
common::{builder_utils, concurrent::Weigher, time::Clock, HousekeeperConfig},
notification::{EvictionListener, RemovalCause},
policy::{EvictionPolicy, ExpirationPolicy},
Expiry,
};
use std::{
collections::hash_map::RandomState,
hash::{BuildHasher, Hash},
marker::PhantomData,
sync::Arc,
time::Duration,
};
#[must_use]
pub struct CacheBuilder<K, V, C> {
name: Option<String>,
max_capacity: Option<u64>,
initial_capacity: Option<usize>,
num_segments: Option<usize>,
weigher: Option<Weigher<K, V>>,
eviction_policy: EvictionPolicy,
eviction_listener: Option<EvictionListener<K, V>>,
expiration_policy: ExpirationPolicy<K, V>,
housekeeper_config: HousekeeperConfig,
invalidator_enabled: bool,
clock: Clock,
cache_type: PhantomData<C>,
}
impl<K, V> Default for CacheBuilder<K, V, Cache<K, V, RandomState>>
where
K: Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
fn default() -> Self {
Self {
name: None,
max_capacity: None,
initial_capacity: None,
num_segments: None,
weigher: None,
eviction_listener: None,
eviction_policy: EvictionPolicy::default(),
expiration_policy: ExpirationPolicy::default(),
housekeeper_config: HousekeeperConfig::default(),
invalidator_enabled: false,
clock: Clock::default(),
cache_type: PhantomData,
}
}
}
impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
where
K: Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
pub fn new(max_capacity: u64) -> Self {
Self {
max_capacity: Some(max_capacity),
..Default::default()
}
}
pub fn segments(
self,
num_segments: usize,
) -> CacheBuilder<K, V, SegmentedCache<K, V, RandomState>> {
assert!(num_segments != 0);
CacheBuilder {
name: self.name,
max_capacity: self.max_capacity,
initial_capacity: self.initial_capacity,
num_segments: Some(num_segments),
weigher: self.weigher,
eviction_policy: self.eviction_policy,
eviction_listener: self.eviction_listener,
expiration_policy: self.expiration_policy,
housekeeper_config: self.housekeeper_config,
invalidator_enabled: self.invalidator_enabled,
clock: self.clock,
cache_type: PhantomData,
}
}
pub fn build(self) -> Cache<K, V, RandomState> {
let build_hasher = RandomState::default();
let exp = &self.expiration_policy;
builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
Cache::with_everything(
self.name,
self.max_capacity,
self.initial_capacity,
build_hasher,
self.weigher,
self.eviction_policy,
self.eviction_listener,
self.expiration_policy,
self.housekeeper_config,
self.invalidator_enabled,
self.clock,
)
}
pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
where
S: BuildHasher + Clone + Send + Sync + 'static,
{
let exp = &self.expiration_policy;
builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
Cache::with_everything(
self.name,
self.max_capacity,
self.initial_capacity,
hasher,
self.weigher,
self.eviction_policy,
self.eviction_listener,
self.expiration_policy,
self.housekeeper_config,
self.invalidator_enabled,
self.clock,
)
}
}
impl<K, V> CacheBuilder<K, V, SegmentedCache<K, V, RandomState>>
where
K: Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
pub fn build(self) -> SegmentedCache<K, V, RandomState> {
let build_hasher = RandomState::default();
let exp = &self.expiration_policy;
builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
SegmentedCache::with_everything(
self.name,
self.max_capacity,
self.initial_capacity,
self.num_segments.unwrap(),
build_hasher,
self.weigher,
self.eviction_policy,
self.eviction_listener,
self.expiration_policy,
self.housekeeper_config,
self.invalidator_enabled,
self.clock,
)
}
pub fn build_with_hasher<S>(self, hasher: S) -> SegmentedCache<K, V, S>
where
S: BuildHasher + Clone + Send + Sync + 'static,
{
let exp = &self.expiration_policy;
builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
SegmentedCache::with_everything(
self.name,
self.max_capacity,
self.initial_capacity,
self.num_segments.unwrap(),
hasher,
self.weigher,
self.eviction_policy,
self.eviction_listener,
self.expiration_policy,
self.housekeeper_config,
self.invalidator_enabled,
self.clock,
)
}
}
impl<K, V, C> CacheBuilder<K, V, C> {
pub fn name(self, name: &str) -> Self {
Self {
name: Some(name.to_string()),
..self
}
}
pub fn max_capacity(self, max_capacity: u64) -> Self {
Self {
max_capacity: Some(max_capacity),
..self
}
}
pub fn initial_capacity(self, number_of_entries: usize) -> Self {
Self {
initial_capacity: Some(number_of_entries),
..self
}
}
pub fn eviction_policy(self, policy: EvictionPolicy) -> Self {
Self {
eviction_policy: policy,
..self
}
}
pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
Self {
weigher: Some(Arc::new(weigher)),
..self
}
}
pub fn eviction_listener(
self,
listener: impl Fn(Arc<K>, V, RemovalCause) + Send + Sync + 'static,
) -> Self {
Self {
eviction_listener: Some(Arc::new(listener)),
..self
}
}
pub fn time_to_live(self, duration: Duration) -> Self {
let mut builder = self;
builder.expiration_policy.set_time_to_live(duration);
builder
}
pub fn time_to_idle(self, duration: Duration) -> Self {
let mut builder = self;
builder.expiration_policy.set_time_to_idle(duration);
builder
}
pub fn expire_after(self, expiry: impl Expiry<K, V> + Send + Sync + 'static) -> Self {
let mut builder = self;
builder.expiration_policy.set_expiry(Arc::new(expiry));
builder
}
#[cfg(test)]
pub(crate) fn housekeeper_config(self, conf: HousekeeperConfig) -> Self {
Self {
housekeeper_config: conf,
..self
}
}
#[cfg(test)]
pub(crate) fn clock(self, clock: Clock) -> Self {
Self { clock, ..self }
}
pub fn support_invalidation_closures(self) -> Self {
Self {
invalidator_enabled: true,
..self
}
}
}
#[cfg(test)]
mod tests {
use super::CacheBuilder;
use std::time::Duration;
#[test]
fn build_cache() {
let cache = CacheBuilder::new(100).build();
let policy = cache.policy();
assert_eq!(policy.max_capacity(), Some(100));
assert_eq!(policy.time_to_live(), None);
assert_eq!(policy.time_to_idle(), None);
assert_eq!(policy.num_segments(), 1);
cache.insert('a', "Alice");
assert_eq!(cache.get(&'a'), Some("Alice"));
let cache = CacheBuilder::new(100)
.time_to_live(Duration::from_secs(45 * 60))
.time_to_idle(Duration::from_secs(15 * 60))
.build();
let config = cache.policy();
assert_eq!(config.max_capacity(), Some(100));
assert_eq!(config.time_to_live(), Some(Duration::from_secs(45 * 60)));
assert_eq!(config.time_to_idle(), Some(Duration::from_secs(15 * 60)));
assert_eq!(config.num_segments(), 1);
cache.insert('a', "Alice");
assert_eq!(cache.get(&'a'), Some("Alice"));
}
#[test]
fn build_segmented_cache() {
let cache = CacheBuilder::new(100).segments(15).build();
let policy = cache.policy();
assert_eq!(policy.max_capacity(), Some(100));
assert!(policy.time_to_live().is_none());
assert!(policy.time_to_idle().is_none());
assert_eq!(policy.num_segments(), 16_usize.next_power_of_two());
cache.insert('b', "Bob");
assert_eq!(cache.get(&'b'), Some("Bob"));
let listener = move |_key, _value, _cause| ();
let builder = CacheBuilder::new(400)
.time_to_live(Duration::from_secs(45 * 60))
.time_to_idle(Duration::from_secs(15 * 60))
.eviction_listener(listener)
.name("tracked_sessions")
.segments(24);
assert!(builder.eviction_listener.is_some());
let cache = builder.build();
let policy = cache.policy();
assert_eq!(policy.max_capacity(), Some(400));
assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
assert_eq!(policy.num_segments(), 24_usize.next_power_of_two());
assert_eq!(cache.name(), Some("tracked_sessions"));
cache.insert('b', "Bob");
assert_eq!(cache.get(&'b'), Some("Bob"));
}
#[test]
#[should_panic(expected = "time_to_live is longer than 1000 years")]
fn build_cache_too_long_ttl() {
let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
let duration = Duration::from_secs(thousand_years_secs);
builder
.time_to_live(duration + Duration::from_secs(1))
.build();
}
#[test]
#[should_panic(expected = "time_to_idle is longer than 1000 years")]
fn build_cache_too_long_tti() {
let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
let duration = Duration::from_secs(thousand_years_secs);
builder
.time_to_idle(duration + Duration::from_secs(1))
.build();
}
}