1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3use std::time::Duration;
4
5use crate::cache::clock::{ClockType, SystemClock};
6use crate::cache::config::weight_calculation::Calculation;
7use crate::cache::errors::Errors;
8use crate::cache::expiration::config::TTLConfig;
9use crate::cache::policy::config::CacheWeightConfig;
10use crate::cache::pool::{BufferSize, PoolSize};
11use crate::cache::types::{IsTimeToLiveSpecified, KeyHash, TotalCapacity, TotalCounters, TotalShards, Weight};
12pub(crate) mod weight_calculation;
13
14pub type HashFn<Key> = dyn Fn(&Key) -> KeyHash + Send + Sync;
17
18pub type WeightCalculationFn<Key, Value> = dyn Fn(&Key, &Value, IsTimeToLiveSpecified) -> Weight + Send + Sync;
21
22const COMMAND_BUFFER_SIZE: usize = 32 * 1024;
26
27const ACCESS_POOL_SIZE: PoolSize = PoolSize(32);
30
31const ACCESS_BUFFER_SIZE: BufferSize = BufferSize(64);
34
35const SHARDS: usize = 256;
38
39const TTL_TICK_DURATION: Duration = Duration::from_secs(5);
42
43pub struct Config<Key, Value>
45 where Key: Hash + 'static,
46 Value: 'static {
47 pub key_hash_fn: Box<HashFn<Key>>,
48 pub weight_calculation_fn: Box<WeightCalculationFn<Key, Value>>,
49 pub clock: ClockType,
50 pub counters: TotalCounters,
51 pub command_buffer_size: usize,
52 pub total_cache_weight: Weight,
53
54 pub(crate) access_pool_size: PoolSize,
55 pub(crate) access_buffer_size: BufferSize,
56 pub(crate) capacity: TotalCapacity,
57 pub(crate) shards: TotalShards,
58
59 ttl_tick_duration: Duration,
60}
61
62impl<Key, Value> Config<Key, Value>
63 where Key: Hash + 'static,
64 Value: 'static {
65 pub(crate) fn ttl_config(&self) -> TTLConfig {
67 TTLConfig::new(self.shards, self.ttl_tick_duration, self.clock.clone_box())
68 }
69
70 pub(crate) fn cache_weight_config(&self) -> CacheWeightConfig {
72 CacheWeightConfig::new(self.capacity, self.shards, self.total_cache_weight)
73 }
74}
75
76pub struct ConfigBuilder<Key, Value>
78 where Key: Hash + 'static,
79 Value: 'static {
80 key_hash_fn: Box<HashFn<Key>>,
81 weight_calculation_fn: Box<WeightCalculationFn<Key, Value>>,
82 clock: ClockType,
83 counters: TotalCounters,
84 capacity: TotalCapacity,
85 command_buffer_size: usize,
86 access_pool_size: PoolSize,
87 access_buffer_size: BufferSize,
88 total_cache_weight: Weight,
89 shards: TotalShards,
90 ttl_tick_duration: Duration,
91}
92
93impl<Key, Value> ConfigBuilder<Key, Value>
94 where Key: Hash + 'static,
95 Value: 'static {
96
97 pub fn new(counters: TotalCounters, capacity: TotalCapacity, cache_weight: Weight) -> Self {
114 assert!(counters > 0, "{}", Errors::TotalCountersGtZero);
115 assert!(capacity > 0, "{}", Errors::TotalCapacityGtZero);
116 assert!(cache_weight > 0, "{}", Errors::TotalCacheWeightGtZero);
117
118 let key_hash_fn = |key: &Key| -> KeyHash {
119 let mut hasher = DefaultHasher::new();
120 key.hash(&mut hasher);
121 hasher.finish()
122 };
123
124 ConfigBuilder {
125 key_hash_fn: Box::new(key_hash_fn),
126 weight_calculation_fn: Box::new(Calculation::perform),
127 clock: SystemClock::boxed(),
128 access_pool_size: ACCESS_POOL_SIZE,
129 access_buffer_size: ACCESS_BUFFER_SIZE,
130 command_buffer_size: COMMAND_BUFFER_SIZE,
131 counters,
132 capacity,
133 total_cache_weight: cache_weight,
134 shards: SHARDS,
135 ttl_tick_duration: TTL_TICK_DURATION,
136 }
137 }
138
139 pub fn key_hash_fn(mut self, key_hash: Box<HashFn<Key>>) -> ConfigBuilder<Key, Value> {
143 self.key_hash_fn = key_hash;
144 self
145 }
146
147 pub fn weight_calculation_fn(mut self, weight_calculation: Box<WeightCalculationFn<Key, Value>>) -> ConfigBuilder<Key, Value> {
153 self.weight_calculation_fn = weight_calculation;
154 self
155 }
156
157 pub fn clock(mut self, clock: ClockType) -> ConfigBuilder<Key, Value> {
159 self.clock = clock;
160 self
161 }
162
163 pub fn access_pool_size(mut self, pool_size: usize) -> ConfigBuilder<Key, Value> {
169 assert!(pool_size > 0, "{}", Errors::PoolSizeGtZero);
170 self.access_pool_size = PoolSize(pool_size);
171 self
172 }
173
174 pub fn access_buffer_size(mut self, buffer_size: usize) -> ConfigBuilder<Key, Value> {
178 assert!(buffer_size > 0, "{}", Errors::BufferSizeGtZero);
179 self.access_buffer_size = BufferSize(buffer_size);
180 self
181 }
182
183 pub fn command_buffer_size(mut self, command_buffer_size: usize) -> ConfigBuilder<Key, Value> {
187 assert!(command_buffer_size > 0, "{}", Errors::CommandBufferSizeGtZero);
188 self.command_buffer_size = command_buffer_size;
189 self
190 }
191
192 pub fn shards(mut self, shards: TotalShards) -> ConfigBuilder<Key, Value> {
196 assert!(shards > 1, "{}", Errors::TotalShardsGtOne);
197 assert!(shards.is_power_of_two(), "{}", Errors::TotalShardsPowerOf2);
198 self.shards = shards;
199 self
200 }
201
202 pub fn ttl_tick_duration(mut self, duration: Duration) -> ConfigBuilder<Key, Value> {
206 self.ttl_tick_duration = duration;
207 self
208 }
209
210 pub fn build(self) -> Config<Key, Value> {
212 Config {
213 key_hash_fn: self.key_hash_fn,
214 weight_calculation_fn: self.weight_calculation_fn,
215 clock: self.clock,
216 access_pool_size: self.access_pool_size,
217 access_buffer_size: self.access_buffer_size,
218 command_buffer_size: self.command_buffer_size,
219 counters: self.counters,
220 capacity: self.capacity,
221 total_cache_weight: self.total_cache_weight,
222 shards: self.shards,
223 ttl_tick_duration: self.ttl_tick_duration,
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use std::time::{Duration, SystemTime};
231
232 use crate::cache::clock::ClockType;
233 use crate::cache::config::{Config, ConfigBuilder};
234 use crate::cache::config::tests::setup::UnixEpochClock;
235 use crate::cache::pool::{BufferSize, PoolSize};
236 use crate::cache::types::IsTimeToLiveSpecified;
237
238 mod setup {
239 use std::time::SystemTime;
240
241 use crate::cache::clock::Clock;
242
243 #[derive(Clone)]
244 pub(crate) struct UnixEpochClock;
245
246 impl Clock for UnixEpochClock {
247 fn now(&self) -> SystemTime {
248 SystemTime::UNIX_EPOCH
249 }
250 }
251 }
252
253 fn test_config_builder() -> ConfigBuilder<&'static str, &'static str>{
254 ConfigBuilder::new(100, 10, 100)
255 }
256
257 #[test]
258 fn key_hash_function() {
259 let builder: ConfigBuilder<&str, &str> = test_config_builder();
260
261 let key_hash_fn = Box::new(|_key: &&str| 1);
262 let config = builder.key_hash_fn(key_hash_fn).build();
263
264 let key = "topic";
265 let hash = (config.key_hash_fn)(&key);
266
267 assert_eq!(1, hash);
268 }
269
270 #[test]
271 fn weight_calculation_function() {
272 let builder: ConfigBuilder<&str, &str> = test_config_builder();
273
274 let weight_calculation_fn = Box::new(|_key: &&str, _value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| 10);
275 let config = builder.weight_calculation_fn(weight_calculation_fn).build();
276
277 let key = "topic";
278 let value = "microservices";
279 let weight = (config.weight_calculation_fn)(&key, &value, false);
280
281 assert_eq!(10, weight);
282 }
283
284 #[test]
285 fn clock() {
286 let builder: ConfigBuilder<&str, &str> = test_config_builder();
287 let clock: ClockType = Box::new(UnixEpochClock {});
288
289 let config = builder.clock(clock).build();
290 assert_eq!(SystemTime::UNIX_EPOCH, config.clock.now());
291 }
292
293 #[test]
294 fn access_pool_size() {
295 let builder: ConfigBuilder<&str, &str> = test_config_builder();
296 let config = builder.access_pool_size(32).build();
297
298 assert_eq!(PoolSize(32), config.access_pool_size);
299 }
300
301 #[test]
302 fn access_buffer_size() {
303 let builder: ConfigBuilder<&str, &str> = test_config_builder();
304 let config = builder.access_buffer_size(64).build();
305
306 assert_eq!(BufferSize(64), config.access_buffer_size);
307 }
308
309 #[test]
310 fn command_buffer_size() {
311 let builder: ConfigBuilder<&str, &str> = test_config_builder();
312 let config = builder.command_buffer_size(1024).build();
313
314 assert_eq!(1024, config.command_buffer_size);
315 }
316
317 #[test]
318 fn counters() {
319 let config: Config<&str, &str> = ConfigBuilder::new(4096, 400, 100).build();
320
321 assert_eq!(4096, config.counters);
322 }
323
324 #[test]
325 fn total_cache_weight() {
326 let builder: ConfigBuilder<&str, &str> = ConfigBuilder::new(100, 10, 1048576);
327 let config = builder.build();
328
329 assert_eq!(1048576, config.total_cache_weight);
330 }
331
332 #[test]
333 fn shards() {
334 let builder: ConfigBuilder<&str, &str> = test_config_builder();
335 let config = builder.shards(16).build();
336
337 assert_eq!(16, config.shards);
338 }
339
340 #[test]
341 fn ttl_tick_duration() {
342 let builder: ConfigBuilder<&str, &str> = test_config_builder();
343 let config = builder.ttl_tick_duration(Duration::from_secs(5)).build();
344
345 assert_eq!(Duration::from_secs(5), config.ttl_tick_duration);
346 }
347
348 #[test]
349 fn ttl_config() {
350 let builder: ConfigBuilder<&str, &str> = test_config_builder();
351 let config = builder.ttl_tick_duration(Duration::from_secs(5)).shards(16).build();
352
353 let ttl_config = config.ttl_config();
354 assert_eq!(16, ttl_config.shards());
355 assert_eq!(Duration::from_secs(5), ttl_config.tick_duration());
356 }
357
358 #[test]
359 fn cache_weight_config() {
360 let builder: ConfigBuilder<&str, &str> = ConfigBuilder::new(100, 10, 200).shards(4);
361 let config = builder.build();
362
363 let cache_weight_config = config.cache_weight_config();
364 assert_eq!(10, cache_weight_config.capacity());
365 assert_eq!(4, cache_weight_config.shards());
366 assert_eq!(200, cache_weight_config.total_cache_weight());
367 }
368
369 #[test]
370 #[should_panic]
371 fn access_pool_size_must_be_greater_than_zero() {
372 let _: Config<&str, &str> = test_config_builder().access_pool_size(0).build();
373 }
374
375 #[test]
376 #[should_panic]
377 fn access_buffer_size_must_be_greater_than_zero() {
378 let _: Config<&str, &str> = test_config_builder().access_buffer_size(0).build();
379 }
380
381 #[test]
382 #[should_panic]
383 fn command_buffer_size_must_be_greater_than_zero() {
384 let _: Config<&str, &str> = test_config_builder().command_buffer_size(0).build();
385 }
386
387 #[test]
388 #[should_panic]
389 fn total_counters_must_be_greater_than_zero() {
390 let _: Config<&str, &str> = ConfigBuilder::new(0, 10, 10).build();
391 }
392
393 #[test]
394 #[should_panic]
395 fn total_capacity_must_be_greater_than_zero() {
396 let _: Config<&str, &str> = ConfigBuilder::new(10, 0, 10).build();
397 }
398
399 #[test]
400 #[should_panic]
401 fn total_cache_weight_must_be_greater_than_zero() {
402 let _: Config<&str, &str> = ConfigBuilder::new(100, 100, 0).build();
403 }
404
405 #[test]
406 #[should_panic]
407 fn shards_must_be_greater_than_one() {
408 let _: Config<&str, &str> = test_config_builder().shards(1).build();
409 }
410
411 #[test]
412 #[should_panic]
413 fn shards_must_be_power_of_2() {
414 let _: Config<&str, &str> = test_config_builder().shards(3).build();
415 }
416}