1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
use super::Cache;
use crate::{common::builder_utils, common::concurrent::Weigher};
use std::{
collections::hash_map::RandomState,
hash::{BuildHasher, Hash},
marker::PhantomData,
sync::Arc,
time::Duration,
};
/// Builds a [`Cache`][cache-struct] or with various configuration knobs.
///
/// [cache-struct]: ./struct.Cache.html
///
/// # Examples
///
/// ```rust
/// use mini_moka::sync::Cache;
/// use std::time::Duration;
///
/// let cache = Cache::builder()
/// // Max 10,000 entries
/// .max_capacity(10_000)
/// // Time to live (TTL): 30 minutes
/// .time_to_live(Duration::from_secs(30 * 60))
/// // Time to idle (TTI): 5 minutes
/// .time_to_idle(Duration::from_secs( 5 * 60))
/// // Create the cache.
/// .build();
///
/// // This entry will expire after 5 minutes (TTI) if there is no get().
/// cache.insert(0, "zero");
///
/// // This get() will extend the entry life for another 5 minutes.
/// cache.get(&0);
///
/// // Even though we keep calling get(), the entry will expire
/// // after 30 minutes (TTL) from the insert().
/// ```
///
#[must_use]
pub struct CacheBuilder<K, V, C> {
max_capacity: Option<u64>,
initial_capacity: Option<usize>,
weigher: Option<Weigher<K, V>>,
time_to_live: Option<Duration>,
time_to_idle: Option<Duration>,
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 {
max_capacity: None,
initial_capacity: None,
weigher: None,
time_to_live: None,
time_to_idle: None,
cache_type: Default::default(),
}
}
}
impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
where
K: Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
/// Construct a new `CacheBuilder` that will be used to build a `Cache` or
/// `SegmentedCache` holding up to `max_capacity` entries.
pub fn new(max_capacity: u64) -> Self {
Self {
max_capacity: Some(max_capacity),
..Default::default()
}
}
/// Builds a `Cache<K, V>`.
///
/// If you want to build a `SegmentedCache<K, V>`, call `segments` method before
/// calling this method.
///
/// # Panics
///
/// Panics if configured with either `time_to_live` or `time_to_idle` higher than
/// 1000 years. This is done to protect against overflow when computing key
/// expiration.
pub fn build(self) -> Cache<K, V, RandomState> {
let build_hasher = RandomState::default();
builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
Cache::with_everything(
self.max_capacity,
self.initial_capacity,
build_hasher,
self.weigher,
self.time_to_live,
self.time_to_idle,
)
}
/// Builds a `Cache<K, V, S>`, with the given `hasher`.
///
/// If you want to build a `SegmentedCache<K, V>`, call `segments` method before
/// calling this method.
///
/// # Panics
///
/// Panics if configured with either `time_to_live` or `time_to_idle` higher than
/// 1000 years. This is done to protect against overflow when computing key
/// expiration.
pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
where
S: BuildHasher + Clone + Send + Sync + 'static,
{
builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
Cache::with_everything(
self.max_capacity,
self.initial_capacity,
hasher,
self.weigher,
self.time_to_live,
self.time_to_idle,
)
}
}
impl<K, V, C> CacheBuilder<K, V, C> {
/// Sets the max capacity of the cache.
pub fn max_capacity(self, max_capacity: u64) -> Self {
Self {
max_capacity: Some(max_capacity),
..self
}
}
/// Sets the initial capacity (number of entries) of the cache.
pub fn initial_capacity(self, number_of_entries: usize) -> Self {
Self {
initial_capacity: Some(number_of_entries),
..self
}
}
/// Sets the weigher closure of the cache.
///
/// The closure should take `&K` and `&V` as the arguments and returns a `u32`
/// representing the relative size of the entry.
pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
Self {
weigher: Some(Arc::new(weigher)),
..self
}
}
/// Sets the time to live of the cache.
///
/// A cached entry will be expired after the specified duration past from
/// `insert`.
///
/// # Panics
///
/// `CacheBuilder::build*` methods will panic if the given `duration` is longer
/// than 1000 years. This is done to protect against overflow when computing key
/// expiration.
pub fn time_to_live(self, duration: Duration) -> Self {
Self {
time_to_live: Some(duration),
..self
}
}
/// Sets the time to idle of the cache.
///
/// A cached entry will be expired after the specified duration past from `get`
/// or `insert`.
///
/// # Panics
///
/// `CacheBuilder::build*` methods will panic if the given `duration` is longer
/// than 1000 years. This is done to protect against overflow when computing key
/// expiration.
pub fn time_to_idle(self, duration: Duration) -> Self {
Self {
time_to_idle: Some(duration),
..self
}
}
}
#[cfg(test)]
mod tests {
use super::CacheBuilder;
use std::time::Duration;
#[test]
fn build_cache() {
// Cache<char, String>
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);
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 policy = cache.policy();
assert_eq!(policy.max_capacity(), Some(100));
assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
cache.insert('a', "Alice");
assert_eq!(cache.get(&'a'), Some("Alice"));
}
#[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();
}
}