1use priority_queue::PriorityQueue;
2use std::cmp::Eq;
3use std::cmp::Reverse;
4use std::collections::HashMap;
5use std::hash::Hash;
6use std::marker::Copy;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, Instant};
9
10use dns_types::protocol::types::*;
11
12#[derive(Debug, Clone)]
18pub struct SharedCache {
19 cache: Arc<Mutex<Cache>>,
20}
21
22const MUTEX_POISON_MESSAGE: &str =
23 "[INTERNAL ERROR] cache mutex poisoned, cannot recover from this - aborting";
24
25impl SharedCache {
26 pub fn new() -> Self {
28 SharedCache {
29 cache: Arc::new(Mutex::new(Cache::new())),
30 }
31 }
32
33 pub fn with_desired_size(desired_size: usize) -> Self {
35 SharedCache {
36 cache: Arc::new(Mutex::new(Cache::with_desired_size(desired_size))),
37 }
38 }
39
40 pub fn get(&self, name: &DomainName, qtype: QueryType) -> Vec<ResourceRecord> {
50 self.cache
51 .lock()
52 .expect(MUTEX_POISON_MESSAGE)
53 .get(name, qtype)
54 }
55
56 pub fn get_without_checking_expiration(
65 &self,
66 name: &DomainName,
67 qtype: QueryType,
68 ) -> Vec<ResourceRecord> {
69 self.cache
70 .lock()
71 .expect(MUTEX_POISON_MESSAGE)
72 .get_without_checking_expiration(name, qtype)
73 }
74
75 pub fn insert(&self, record: ResourceRecord) {
85 if record.ttl > 0 {
86 let mut cache = self.cache.lock().expect(MUTEX_POISON_MESSAGE);
87 cache.insert(record);
88 }
89 }
90
91 pub fn insert_all(&self, records: Vec<ResourceRecord>) {
104 let mut cache = self.cache.lock().expect(MUTEX_POISON_MESSAGE);
105 for record in records {
106 if record.ttl > 0 {
107 cache.insert(record);
108 }
109 }
110 }
111
112 pub fn prune(&self) -> (bool, usize, usize, usize) {
121 self.cache.lock().expect(MUTEX_POISON_MESSAGE).prune()
122 }
123}
124
125impl Default for SharedCache {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[derive(Debug, Clone)]
135pub struct Cache {
136 inner: PartitionedCache<DomainName, RecordType, RecordTypeWithData>,
137}
138
139impl Default for Cache {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145impl Cache {
146 pub fn new() -> Self {
148 Self {
149 inner: PartitionedCache::new(),
150 }
151 }
152
153 pub fn with_desired_size(desired_size: usize) -> Self {
158 Self {
159 inner: PartitionedCache::with_desired_size(desired_size),
160 }
161 }
162
163 pub fn get(&mut self, name: &DomainName, qtype: QueryType) -> Vec<ResourceRecord> {
169 let mut rrs = self.get_without_checking_expiration(name, qtype);
170 rrs.retain(|rr| rr.ttl > 0);
171 rrs
172 }
173
174 pub fn get_without_checking_expiration(
179 &mut self,
180 name: &DomainName,
181 qtype: QueryType,
182 ) -> Vec<ResourceRecord> {
183 let now = Instant::now();
184 let mut rrs = Vec::new();
185 match qtype {
186 QueryType::Wildcard => {
187 if let Some(records) = self.inner.get_partition_without_checking_expiration(name) {
188 for tuples in records.values() {
189 to_rrs(name, now, tuples, &mut rrs);
190 }
191 }
192 }
193 QueryType::Record(rtype) => {
194 if let Some(tuples) = self.inner.get_without_checking_expiration(name, &rtype) {
195 to_rrs(name, now, tuples, &mut rrs);
196 }
197 }
198 _ => (),
199 }
200
201 rrs
202 }
203
204 pub fn insert(&mut self, record: ResourceRecord) {
206 self.inner.upsert(
207 record.name,
208 record.rtype_with_data.rtype(),
209 record.rtype_with_data,
210 Duration::from_secs(record.ttl.into()),
211 );
212 }
213
214 pub fn prune(&mut self) -> (bool, usize, usize, usize) {
219 self.inner.prune()
220 }
221}
222
223fn to_rrs(
226 name: &DomainName,
227 now: Instant,
228 tuples: &[(RecordTypeWithData, Instant)],
229 rrs: &mut Vec<ResourceRecord>,
230) {
231 for (rtype, expires) in tuples {
232 rrs.push(ResourceRecord {
233 name: name.clone(),
234 rtype_with_data: rtype.clone(),
235 rclass: RecordClass::IN,
236 ttl: expires
237 .saturating_duration_since(now)
238 .as_secs()
239 .try_into()
240 .unwrap_or(u32::MAX),
241 });
242 }
243}
244
245#[derive(Debug, Clone)]
246pub struct PartitionedCache<K1: Eq + Hash, K2: Eq + Hash, V> {
247 partitions: HashMap<K1, Partition<K2, V>>,
249
250 access_priority: PriorityQueue<K1, Reverse<Instant>>,
257
258 expiry_priority: PriorityQueue<K1, Reverse<Instant>>,
264
265 current_size: usize,
269
270 desired_size: usize,
272}
273
274#[derive(Debug, Clone, Eq, PartialEq)]
276struct Partition<K: Eq + Hash, V> {
277 last_read: Instant,
279
280 next_expiry: Instant,
284
285 size: usize,
289
290 records: HashMap<K, Vec<(V, Instant)>>,
292}
293
294impl<K1: Clone + Eq + Hash, K2: Copy + Eq + Hash, V: PartialEq> Default
295 for PartitionedCache<K1, K2, V>
296{
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302impl<K1: Clone + Eq + Hash, K2: Copy + Eq + Hash, V: PartialEq> PartitionedCache<K1, K2, V> {
303 pub fn new() -> Self {
305 Self::with_desired_size(512)
306 }
307
308 pub fn with_desired_size(desired_size: usize) -> Self {
313 Self {
314 partitions: HashMap::with_capacity(desired_size / 2),
318 access_priority: PriorityQueue::with_capacity(desired_size),
319 expiry_priority: PriorityQueue::with_capacity(desired_size),
320 current_size: 0,
321 desired_size,
322 }
323 }
324
325 pub fn get_partition_without_checking_expiration(
330 &mut self,
331 partition_key: &K1,
332 ) -> Option<&HashMap<K2, Vec<(V, Instant)>>> {
333 if let Some(partition) = self.partitions.get_mut(partition_key) {
334 partition.last_read = Instant::now();
335 self.access_priority
336 .change_priority(partition_key, Reverse(partition.last_read));
337 return Some(&partition.records);
338 }
339
340 None
341 }
342
343 pub fn get_without_checking_expiration(
348 &mut self,
349 partition_key: &K1,
350 record_key: &K2,
351 ) -> Option<&[(V, Instant)]> {
352 if let Some(partition) = self.partitions.get_mut(partition_key) {
353 if let Some(tuples) = partition.records.get(record_key) {
354 partition.last_read = Instant::now();
355 self.access_priority
356 .change_priority(partition_key, Reverse(partition.last_read));
357 return Some(tuples);
358 }
359 }
360
361 None
362 }
363
364 pub fn upsert(&mut self, partition_key: K1, record_key: K2, value: V, ttl: Duration) {
367 let now = Instant::now();
368 let expiry = now + ttl;
369 let tuple = (value, expiry);
370 if let Some(partition) = self.partitions.get_mut(&partition_key) {
371 let mut recalculate_next_expiry = false;
372
373 if let Some(tuples) = partition.records.get_mut(&record_key) {
374 for i in 0..tuples.len() {
375 let t = &tuples[i];
376 if t.0 == tuple.0 {
377 partition.size -= 1;
378 self.current_size -= 1;
379 recalculate_next_expiry = t.1 == partition.next_expiry;
380 tuples.swap_remove(i);
381 break;
382 }
383 }
384
385 tuples.push(tuple);
386 } else {
387 partition.records.insert(record_key, vec![tuple]);
388 }
389 partition.last_read = now;
390 partition.size += 1;
391 self.access_priority
392 .change_priority(&partition_key, Reverse(partition.last_read));
393 if expiry < partition.next_expiry {
394 partition.next_expiry = expiry;
395 self.expiry_priority
396 .change_priority(&partition_key, Reverse(partition.next_expiry));
397 } else if recalculate_next_expiry {
398 partition.next_expiry = expiry;
401 for tuples in partition.records.values() {
402 for (_, expires) in tuples {
403 if *expires < partition.next_expiry {
404 partition.next_expiry = *expires;
405 }
406 }
407 }
408 self.expiry_priority
409 .change_priority(&partition_key, Reverse(partition.next_expiry));
410 }
411 } else {
412 let mut records = HashMap::new();
413 records.insert(record_key, vec![tuple]);
414 let partition = Partition {
415 last_read: now,
416 next_expiry: expiry,
417 size: 1,
418 records,
419 };
420 self.access_priority
421 .push(partition_key.clone(), Reverse(partition.last_read));
422 self.expiry_priority
423 .push(partition_key.clone(), Reverse(partition.next_expiry));
424 self.partitions.insert(partition_key, partition);
425 }
426
427 self.current_size += 1;
428 }
429
430 pub fn remove_expired(&mut self) -> usize {
434 let mut pruned = 0;
435
436 loop {
437 let before = pruned;
438 pruned += self.remove_expired_step();
439 if before == pruned {
440 break;
441 }
442 }
443
444 pruned
445 }
446
447 pub fn prune(&mut self) -> (bool, usize, usize, usize) {
453 let has_overflowed = self.current_size > self.desired_size;
454 let num_expired = self.remove_expired();
455 let mut num_pruned = 0;
456
457 while self.current_size > self.desired_size {
458 num_pruned += self.remove_least_recently_used();
459 }
460
461 (has_overflowed, self.current_size, num_expired, num_pruned)
462 }
463
464 fn remove_expired_step(&mut self) -> usize {
470 if let Some((partition_key, Reverse(expiry))) = self.expiry_priority.pop() {
471 let now = Instant::now();
472
473 if expiry > now {
474 self.expiry_priority.push(partition_key, Reverse(expiry));
475 return 0;
476 }
477
478 if let Some(partition) = self.partitions.get_mut(&partition_key) {
479 let mut pruned = 0;
480
481 let record_keys = partition.records.keys().copied().collect::<Vec<K2>>();
482 let mut next_expiry = None;
483 for rkey in record_keys {
484 if let Some(tuples) = partition.records.get_mut(&rkey) {
485 let len = tuples.len();
486 tuples.retain(|(_, expiry)| expiry > &now);
487 pruned += len - tuples.len();
488 for (_, expiry) in tuples {
489 match next_expiry {
490 None => next_expiry = Some(*expiry),
491 Some(t) if *expiry < t => next_expiry = Some(*expiry),
492 _ => (),
493 }
494 }
495 }
496 }
497
498 partition.size -= pruned;
499
500 if let Some(ne) = next_expiry {
501 partition.next_expiry = ne;
502 self.expiry_priority.push(partition_key, Reverse(ne));
503 } else {
504 self.partitions.remove(&partition_key);
505 self.access_priority.remove(&partition_key);
506 }
507
508 self.current_size -= pruned;
509 pruned
510 } else {
511 self.access_priority.remove(&partition_key);
512 0
513 }
514 } else {
515 0
516 }
517 }
518
519 fn remove_least_recently_used(&mut self) -> usize {
524 if let Some((partition_key, _)) = self.access_priority.pop() {
525 self.expiry_priority.remove(&partition_key);
526
527 if let Some(partition) = self.partitions.remove(&partition_key) {
528 let pruned = partition.size;
529 self.current_size -= pruned;
530 pruned
531 } else {
532 0
533 }
534 } else {
535 0
536 }
537 }
538}
539
540#[allow(clippy::missing_panics_doc)]
541pub mod test_util {
542 use super::*;
543
544 pub fn assert_cache_response(original: &ResourceRecord, response: &[ResourceRecord]) {
548 assert_eq!(1, response.len());
549 let cached = response[0].clone();
550
551 assert_eq!(original.name, cached.name);
552 assert_eq!(original.rtype_with_data, cached.rtype_with_data);
553 assert_eq!(RecordClass::IN, cached.rclass);
554 assert!(original.ttl >= cached.ttl);
555 }
556
557 pub fn assert_current_size(expected: usize, cache: &Cache) {
559 if expected != cache.inner.current_size {
560 dbg!(&cache.inner.partitions);
561 assert_eq!(expected, cache.inner.current_size);
562 }
563 }
564
565 pub fn assert_expires(expected: usize, cache: &mut Cache) {
567 assert_eq!(expected, cache.inner.remove_expired());
568 }
569
570 pub fn assert_invariants(cache: &Cache) {
577 assert_eq!(
578 cache.inner.current_size,
579 cache
580 .inner
581 .partitions
582 .values()
583 .map(|e| e.size)
584 .sum::<usize>()
585 );
586
587 assert_eq!(
588 cache.inner.partitions.len(),
589 cache.inner.access_priority.len()
590 );
591 assert_eq!(
592 cache.inner.partitions.len(),
593 cache.inner.expiry_priority.len()
594 );
595
596 let mut access_priority = PriorityQueue::new();
597 let mut expiry_priority = PriorityQueue::new();
598
599 for (name, partition) in &cache.inner.partitions {
600 assert_eq!(
601 partition.size,
602 partition.records.values().map(Vec::len).sum::<usize>()
603 );
604
605 let mut min_expires = None;
606 for (rtype, tuples) in &partition.records {
607 for (rtype_with_data, expires) in tuples {
608 assert_eq!(*rtype, rtype_with_data.rtype());
609
610 if let Some(e) = min_expires {
611 if *expires < e {
612 min_expires = Some(*expires);
613 }
614 } else {
615 min_expires = Some(*expires);
616 }
617 }
618 }
619
620 assert_eq!(Some(partition.next_expiry), min_expires);
621
622 access_priority.push(name.clone(), Reverse(partition.last_read));
623 expiry_priority.push(name.clone(), Reverse(partition.next_expiry));
624 }
625
626 assert_eq!(cache.inner.access_priority, access_priority);
627 assert_eq!(cache.inner.expiry_priority, expiry_priority);
628 }
629}