1use std::collections::HashMap;
8
9use super::CacheStats;
10use super::entry::CacheEntry;
11use crate::NodeAddr;
12use crate::tree::TreeCoordinate;
13
14pub const DEFAULT_COORD_CACHE_SIZE: usize = 50_000;
16
17pub const DEFAULT_COORD_CACHE_TTL_MS: u64 = 300_000;
19
20#[derive(Clone, Debug)]
26pub struct CoordCache {
27 entries: HashMap<NodeAddr, CacheEntry>,
29 max_entries: usize,
31 default_ttl_ms: u64,
33}
34
35impl CoordCache {
36 pub fn new(max_entries: usize, default_ttl_ms: u64) -> Self {
38 Self {
39 entries: HashMap::with_capacity(max_entries.min(1000)),
40 max_entries,
41 default_ttl_ms,
42 }
43 }
44
45 pub fn with_defaults() -> Self {
47 Self::new(DEFAULT_COORD_CACHE_SIZE, DEFAULT_COORD_CACHE_TTL_MS)
48 }
49
50 pub fn max_entries(&self) -> usize {
52 self.max_entries
53 }
54
55 pub fn default_ttl_ms(&self) -> u64 {
57 self.default_ttl_ms
58 }
59
60 pub fn set_default_ttl_ms(&mut self, ttl_ms: u64) {
62 self.default_ttl_ms = ttl_ms;
63 }
64
65 pub fn insert(&mut self, addr: NodeAddr, coords: TreeCoordinate, current_time_ms: u64) {
67 if let Some(entry) = self.entries.get_mut(&addr) {
69 entry.update(coords, current_time_ms, self.default_ttl_ms);
70 return;
71 }
72
73 if self.entries.len() >= self.max_entries {
75 self.evict_one(current_time_ms);
76 }
77
78 let entry = CacheEntry::new(coords, current_time_ms, self.default_ttl_ms);
79 self.entries.insert(addr, entry);
80 }
81
82 pub fn insert_with_path_mtu(
87 &mut self,
88 addr: NodeAddr,
89 coords: TreeCoordinate,
90 current_time_ms: u64,
91 path_mtu: u16,
92 ) {
93 if let Some(entry) = self.entries.get_mut(&addr) {
94 entry.update(coords, current_time_ms, self.default_ttl_ms);
95 entry.set_path_mtu(path_mtu);
96 return;
97 }
98
99 if self.entries.len() >= self.max_entries {
100 self.evict_one(current_time_ms);
101 }
102
103 let mut entry = CacheEntry::new(coords, current_time_ms, self.default_ttl_ms);
104 entry.set_path_mtu(path_mtu);
105 self.entries.insert(addr, entry);
106 }
107
108 pub fn insert_with_ttl(
110 &mut self,
111 addr: NodeAddr,
112 coords: TreeCoordinate,
113 current_time_ms: u64,
114 ttl_ms: u64,
115 ) {
116 if let Some(entry) = self.entries.get_mut(&addr) {
117 entry.update(coords, current_time_ms, ttl_ms);
118 return;
119 }
120
121 if self.entries.len() >= self.max_entries {
122 self.evict_one(current_time_ms);
123 }
124
125 let entry = CacheEntry::new(coords, current_time_ms, ttl_ms);
126 self.entries.insert(addr, entry);
127 }
128
129 pub fn get(&self, addr: &NodeAddr, current_time_ms: u64) -> Option<&TreeCoordinate> {
131 self.entries.get(addr).and_then(|entry| {
132 if entry.is_expired(current_time_ms) {
133 None
134 } else {
135 Some(entry.coords())
136 }
137 })
138 }
139
140 pub fn get_and_touch(
142 &mut self,
143 addr: &NodeAddr,
144 current_time_ms: u64,
145 ) -> Option<&TreeCoordinate> {
146 if let Some(entry) = self.entries.get(addr)
148 && entry.is_expired(current_time_ms)
149 {
150 self.entries.remove(addr);
151 return None;
152 }
153
154 if let Some(entry) = self.entries.get_mut(addr) {
156 entry.refresh(current_time_ms, self.default_ttl_ms);
157 Some(entry.coords())
158 } else {
159 None
160 }
161 }
162
163 pub fn get_entry(&self, addr: &NodeAddr) -> Option<&CacheEntry> {
165 self.entries.get(addr)
166 }
167
168 pub fn remove(&mut self, addr: &NodeAddr) -> Option<CacheEntry> {
170 self.entries.remove(addr)
171 }
172
173 pub fn contains(&self, addr: &NodeAddr, current_time_ms: u64) -> bool {
175 self.get(addr, current_time_ms).is_some()
176 }
177
178 pub fn len(&self) -> usize {
180 self.entries.len()
181 }
182
183 pub fn is_empty(&self) -> bool {
185 self.entries.is_empty()
186 }
187
188 pub fn iter(&self, current_time_ms: u64) -> impl Iterator<Item = (&NodeAddr, &CacheEntry)> {
190 self.entries
191 .iter()
192 .filter(move |(_, entry)| !entry.is_expired(current_time_ms))
193 }
194
195 pub fn purge_expired(&mut self, current_time_ms: u64) -> usize {
197 let before = self.entries.len();
198 self.entries
199 .retain(|_, entry| !entry.is_expired(current_time_ms));
200 before - self.entries.len()
201 }
202
203 pub fn clear(&mut self) {
205 self.entries.clear();
206 }
207
208 pub fn invalidate_via_node(&mut self, node_addr: &NodeAddr) -> usize {
214 let len_before = self.entries.len();
215 self.entries
216 .retain(|_, entry| !entry.coords().contains(node_addr));
217 len_before - self.entries.len()
218 }
219
220 pub fn invalidate_other_roots(&mut self, current_root: &NodeAddr) -> usize {
226 let len_before = self.entries.len();
227 self.entries
228 .retain(|_, entry| entry.coords().root_id() == current_root);
229 len_before - self.entries.len()
230 }
231
232 fn evict_one(&mut self, current_time_ms: u64) {
234 let expired_key = self
236 .entries
237 .iter()
238 .find(|(_, e)| e.is_expired(current_time_ms))
239 .map(|(k, _)| *k);
240
241 if let Some(key) = expired_key {
242 self.entries.remove(&key);
243 return;
244 }
245
246 let lru_key = self
248 .entries
249 .iter()
250 .max_by_key(|(_, e)| e.idle_time(current_time_ms))
251 .map(|(k, _)| *k);
252
253 if let Some(key) = lru_key {
254 self.entries.remove(&key);
255 }
256 }
257
258 pub fn stats(&self, current_time_ms: u64) -> CacheStats {
260 let mut expired = 0;
261 let mut total_age = 0u64;
262
263 for entry in self.entries.values() {
264 if entry.is_expired(current_time_ms) {
265 expired += 1;
266 }
267 total_age += entry.age(current_time_ms);
268 }
269
270 CacheStats {
271 entries: self.entries.len(),
272 max_entries: self.max_entries,
273 expired,
274 avg_age_ms: if self.entries.is_empty() {
275 0
276 } else {
277 total_age / self.entries.len() as u64
278 },
279 }
280 }
281}
282
283impl Default for CoordCache {
284 fn default() -> Self {
285 Self::with_defaults()
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 fn make_node_addr(val: u8) -> NodeAddr {
294 let mut bytes = [0u8; 16];
295 bytes[0] = val;
296 NodeAddr::from_bytes(bytes)
297 }
298
299 fn make_coords(ids: &[u8]) -> TreeCoordinate {
300 TreeCoordinate::from_addrs(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
301 }
302
303 #[test]
304 fn test_coord_cache_basic() {
305 let mut cache = CoordCache::new(100, 1000);
306 let addr = make_node_addr(1);
307 let coords = make_coords(&[1, 0]);
308
309 cache.insert(addr, coords.clone(), 0);
310
311 assert!(cache.contains(&addr, 0));
312 assert_eq!(cache.get(&addr, 0), Some(&coords));
313 assert_eq!(cache.len(), 1);
314 }
315
316 #[test]
317 fn test_coord_cache_expiry() {
318 let mut cache = CoordCache::new(100, 1000);
319 let addr = make_node_addr(1);
320 let coords = make_coords(&[1, 0]);
321
322 cache.insert(addr, coords, 0);
323
324 assert!(cache.contains(&addr, 500));
325 assert!(!cache.contains(&addr, 1500));
326 }
327
328 #[test]
329 fn test_coord_cache_update() {
330 let mut cache = CoordCache::new(100, 1000);
331 let addr = make_node_addr(1);
332
333 cache.insert(addr, make_coords(&[1, 0]), 0);
334 cache.insert(addr, make_coords(&[1, 2, 0]), 500);
335
336 assert_eq!(cache.len(), 1);
337 let coords = cache.get(&addr, 500).unwrap();
338 assert_eq!(coords.depth(), 2);
339 }
340
341 #[test]
342 fn test_coord_cache_eviction() {
343 let mut cache = CoordCache::new(2, 10000);
344
345 let addr1 = make_node_addr(1);
346 let addr2 = make_node_addr(2);
347 let addr3 = make_node_addr(3);
348
349 cache.insert(addr1, make_coords(&[1, 0]), 0);
350 cache.insert(addr2, make_coords(&[2, 0]), 100);
351
352 let _ = cache.get_and_touch(&addr2, 200);
354
355 cache.insert(addr3, make_coords(&[3, 0]), 300);
357
358 assert!(!cache.contains(&addr1, 300));
359 assert!(cache.contains(&addr2, 300));
360 assert!(cache.contains(&addr3, 300));
361 }
362
363 #[test]
364 fn test_coord_cache_evict_expired_first() {
365 let mut cache = CoordCache::new(2, 100);
366
367 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
368 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
369
370 cache.insert(make_node_addr(3), make_coords(&[3, 0]), 150);
372
373 assert!(!cache.contains(&make_node_addr(1), 150));
375 assert!(cache.contains(&make_node_addr(2), 150));
376 assert!(cache.contains(&make_node_addr(3), 150));
377 }
378
379 #[test]
380 fn test_coord_cache_purge_expired() {
381 let mut cache = CoordCache::new(100, 100);
382
383 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0); cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50); cache.insert(make_node_addr(3), make_coords(&[3, 0]), 200); assert_eq!(cache.len(), 3);
388
389 let purged = cache.purge_expired(151); assert_eq!(purged, 2);
393 assert_eq!(cache.len(), 1);
394 assert!(cache.contains(&make_node_addr(3), 151));
395 }
396
397 #[test]
398 fn test_coord_cache_stats() {
399 let mut cache = CoordCache::new(100, 100);
400
401 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
402 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
403
404 let stats = cache.stats(150);
405
406 assert_eq!(stats.entries, 2);
407 assert_eq!(stats.max_entries, 100);
408 assert_eq!(stats.expired, 1); assert!(stats.avg_age_ms > 0);
410 }
411
412 #[test]
413 fn test_coord_cache_insert_with_ttl() {
414 let mut cache = CoordCache::new(100, 1000);
415 let addr = make_node_addr(1);
416
417 cache.insert_with_ttl(addr, make_coords(&[1, 0]), 0, 200);
418
419 assert!(cache.contains(&addr, 100));
421 assert!(!cache.contains(&addr, 201));
422 }
423
424 #[test]
425 fn test_coord_cache_insert_with_ttl_update() {
426 let mut cache = CoordCache::new(100, 1000);
427 let addr = make_node_addr(1);
428
429 cache.insert_with_ttl(addr, make_coords(&[1, 0]), 0, 200);
430 cache.insert_with_ttl(addr, make_coords(&[1, 2, 0]), 100, 300);
431
432 assert_eq!(cache.len(), 1);
433 let coords = cache.get(&addr, 100).unwrap();
434 assert_eq!(coords.depth(), 2);
435 assert!(cache.contains(&addr, 399));
437 assert!(!cache.contains(&addr, 401));
438 }
439
440 #[test]
441 fn test_coord_cache_get_and_touch_removes_expired() {
442 let mut cache = CoordCache::new(100, 100);
443 let addr = make_node_addr(1);
444
445 cache.insert(addr, make_coords(&[1, 0]), 0);
446 assert_eq!(cache.len(), 1);
447
448 let result = cache.get_and_touch(&addr, 200);
450 assert!(result.is_none());
451 assert_eq!(cache.len(), 0);
453 }
454
455 #[test]
456 fn test_coord_cache_get_entry() {
457 let mut cache = CoordCache::new(100, 1000);
458 let addr = make_node_addr(1);
459
460 cache.insert(addr, make_coords(&[1, 0]), 500);
461
462 let entry = cache.get_entry(&addr).unwrap();
463 assert_eq!(entry.created_at(), 500);
464 assert_eq!(entry.expires_at(), 1500);
465
466 assert!(cache.get_entry(&make_node_addr(99)).is_none());
467 }
468
469 #[test]
470 fn test_coord_cache_remove() {
471 let mut cache = CoordCache::new(100, 1000);
472 let addr = make_node_addr(1);
473
474 cache.insert(addr, make_coords(&[1, 0]), 0);
475 assert_eq!(cache.len(), 1);
476
477 let removed = cache.remove(&addr);
478 assert!(removed.is_some());
479 assert_eq!(cache.len(), 0);
480
481 assert!(cache.remove(&addr).is_none());
483 }
484
485 #[test]
486 fn test_coord_cache_clear_and_is_empty() {
487 let mut cache = CoordCache::new(100, 1000);
488
489 assert!(cache.is_empty());
490
491 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
492 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 0);
493
494 assert!(!cache.is_empty());
495
496 cache.clear();
497 assert!(cache.is_empty());
498 assert_eq!(cache.len(), 0);
499 }
500
501 #[test]
502 fn test_coord_cache_invalidate_via_node_is_surgical() {
503 let mut cache = CoordCache::new(100, 1000);
504 let node = make_node_addr(1);
505 let downstream = make_node_addr(2);
506 let sibling = make_node_addr(3);
507
508 cache.insert(downstream, make_coords(&[2, 1, 0]), 0);
509 cache.insert(sibling, make_coords(&[3, 4, 0]), 0);
510
511 assert_eq!(cache.invalidate_via_node(&node), 1);
512 assert!(!cache.contains(&downstream, 0));
513 assert!(cache.contains(&sibling, 0));
514 }
515
516 #[test]
517 fn test_coord_cache_invalidate_other_roots_keeps_current_root() {
518 let mut cache = CoordCache::new(100, 1000);
519 let current_root = make_node_addr(0);
520 let current = make_node_addr(2);
521 let stale = make_node_addr(3);
522
523 cache.insert(current, make_coords(&[2, 1, 0]), 0);
524 cache.insert(stale, make_coords(&[3, 4, 9]), 0);
525
526 assert_eq!(cache.invalidate_other_roots(¤t_root), 1);
527 assert!(cache.contains(¤t, 0));
528 assert!(!cache.contains(&stale, 0));
529 }
530
531 #[test]
532 fn test_coord_cache_default() {
533 let cache = CoordCache::default();
534
535 assert_eq!(cache.max_entries(), DEFAULT_COORD_CACHE_SIZE);
536 assert_eq!(cache.default_ttl_ms(), DEFAULT_COORD_CACHE_TTL_MS);
537 assert!(cache.is_empty());
538 }
539
540 #[test]
541 fn test_coord_cache_set_default_ttl() {
542 let mut cache = CoordCache::new(100, 1000);
543 let addr = make_node_addr(1);
544
545 cache.set_default_ttl_ms(200);
546 assert_eq!(cache.default_ttl_ms(), 200);
547
548 cache.insert(addr, make_coords(&[1, 0]), 0);
549 assert!(cache.contains(&addr, 100));
551 assert!(!cache.contains(&addr, 201));
552 }
553
554 #[test]
555 fn test_coord_cache_stats_empty() {
556 let cache = CoordCache::new(100, 1000);
557 let stats = cache.stats(0);
558
559 assert_eq!(stats.entries, 0);
560 assert_eq!(stats.max_entries, 100);
561 assert_eq!(stats.expired, 0);
562 assert_eq!(stats.avg_age_ms, 0);
563 }
564}