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(
88 &mut self,
89 addr: NodeAddr,
90 coords: TreeCoordinate,
91 current_time_ms: u64,
92 path_mtu: u16,
93 ) {
94 if let Some(entry) = self.entries.get_mut(&addr) {
95 entry.update(coords, current_time_ms, self.default_ttl_ms);
96 let path_mtu = entry
97 .path_mtu()
98 .map_or(path_mtu, |existing| existing.min(path_mtu));
99 entry.set_path_mtu(path_mtu);
100 return;
101 }
102
103 if self.entries.len() >= self.max_entries {
104 self.evict_one(current_time_ms);
105 }
106
107 let mut entry = CacheEntry::new(coords, current_time_ms, self.default_ttl_ms);
108 entry.set_path_mtu(path_mtu);
109 self.entries.insert(addr, entry);
110 }
111
112 pub fn insert_with_ttl(
114 &mut self,
115 addr: NodeAddr,
116 coords: TreeCoordinate,
117 current_time_ms: u64,
118 ttl_ms: u64,
119 ) {
120 if let Some(entry) = self.entries.get_mut(&addr) {
121 entry.update(coords, current_time_ms, ttl_ms);
122 return;
123 }
124
125 if self.entries.len() >= self.max_entries {
126 self.evict_one(current_time_ms);
127 }
128
129 let entry = CacheEntry::new(coords, current_time_ms, ttl_ms);
130 self.entries.insert(addr, entry);
131 }
132
133 pub fn get(&self, addr: &NodeAddr, current_time_ms: u64) -> Option<&TreeCoordinate> {
135 self.entries.get(addr).and_then(|entry| {
136 if entry.is_expired(current_time_ms) {
137 None
138 } else {
139 Some(entry.coords())
140 }
141 })
142 }
143
144 pub fn get_and_touch(
146 &mut self,
147 addr: &NodeAddr,
148 current_time_ms: u64,
149 ) -> Option<&TreeCoordinate> {
150 if let Some(entry) = self.entries.get(addr)
152 && entry.is_expired(current_time_ms)
153 {
154 self.entries.remove(addr);
155 return None;
156 }
157
158 if let Some(entry) = self.entries.get_mut(addr) {
160 entry.refresh(current_time_ms, self.default_ttl_ms);
161 Some(entry.coords())
162 } else {
163 None
164 }
165 }
166
167 pub fn get_entry(&self, addr: &NodeAddr) -> Option<&CacheEntry> {
169 self.entries.get(addr)
170 }
171
172 pub fn remove(&mut self, addr: &NodeAddr) -> Option<CacheEntry> {
174 self.entries.remove(addr)
175 }
176
177 pub fn contains(&self, addr: &NodeAddr, current_time_ms: u64) -> bool {
179 self.get(addr, current_time_ms).is_some()
180 }
181
182 pub fn len(&self) -> usize {
184 self.entries.len()
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.entries.is_empty()
190 }
191
192 pub fn iter(&self, current_time_ms: u64) -> impl Iterator<Item = (&NodeAddr, &CacheEntry)> {
194 self.entries
195 .iter()
196 .filter(move |(_, entry)| !entry.is_expired(current_time_ms))
197 }
198
199 pub fn purge_expired(&mut self, current_time_ms: u64) -> usize {
201 let before = self.entries.len();
202 self.entries
203 .retain(|_, entry| !entry.is_expired(current_time_ms));
204 before - self.entries.len()
205 }
206
207 pub fn clear(&mut self) {
209 self.entries.clear();
210 }
211
212 pub fn invalidate_via_node(&mut self, node_addr: &NodeAddr) -> usize {
218 let len_before = self.entries.len();
219 self.entries
220 .retain(|_, entry| !entry.coords().contains(node_addr));
221 len_before - self.entries.len()
222 }
223
224 pub fn invalidate_other_roots(&mut self, current_root: &NodeAddr) -> usize {
230 let len_before = self.entries.len();
231 self.entries
232 .retain(|_, entry| entry.coords().root_id() == current_root);
233 len_before - self.entries.len()
234 }
235
236 fn evict_one(&mut self, current_time_ms: u64) {
238 let expired_key = self
240 .entries
241 .iter()
242 .find(|(_, e)| e.is_expired(current_time_ms))
243 .map(|(k, _)| *k);
244
245 if let Some(key) = expired_key {
246 self.entries.remove(&key);
247 return;
248 }
249
250 let lru_key = self
252 .entries
253 .iter()
254 .max_by_key(|(_, e)| e.idle_time(current_time_ms))
255 .map(|(k, _)| *k);
256
257 if let Some(key) = lru_key {
258 self.entries.remove(&key);
259 }
260 }
261
262 pub fn stats(&self, current_time_ms: u64) -> CacheStats {
264 let mut expired = 0;
265 let mut total_age = 0u64;
266
267 for entry in self.entries.values() {
268 if entry.is_expired(current_time_ms) {
269 expired += 1;
270 }
271 total_age += entry.age(current_time_ms);
272 }
273
274 CacheStats {
275 entries: self.entries.len(),
276 max_entries: self.max_entries,
277 expired,
278 avg_age_ms: if self.entries.is_empty() {
279 0
280 } else {
281 total_age / self.entries.len() as u64
282 },
283 }
284 }
285}
286
287impl Default for CoordCache {
288 fn default() -> Self {
289 Self::with_defaults()
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn make_node_addr(val: u8) -> NodeAddr {
298 let mut bytes = [0u8; 16];
299 bytes[0] = val;
300 NodeAddr::from_bytes(bytes)
301 }
302
303 fn make_coords(ids: &[u8]) -> TreeCoordinate {
304 TreeCoordinate::from_addrs(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
305 }
306
307 #[test]
308 fn test_coord_cache_basic() {
309 let mut cache = CoordCache::new(100, 1000);
310 let addr = make_node_addr(1);
311 let coords = make_coords(&[1, 0]);
312
313 cache.insert(addr, coords.clone(), 0);
314
315 assert!(cache.contains(&addr, 0));
316 assert_eq!(cache.get(&addr, 0), Some(&coords));
317 assert_eq!(cache.len(), 1);
318 }
319
320 #[test]
321 fn test_coord_cache_expiry() {
322 let mut cache = CoordCache::new(100, 1000);
323 let addr = make_node_addr(1);
324 let coords = make_coords(&[1, 0]);
325
326 cache.insert(addr, coords, 0);
327
328 assert!(cache.contains(&addr, 500));
329 assert!(!cache.contains(&addr, 1500));
330 }
331
332 #[test]
333 fn test_coord_cache_update() {
334 let mut cache = CoordCache::new(100, 1000);
335 let addr = make_node_addr(1);
336
337 cache.insert(addr, make_coords(&[1, 0]), 0);
338 cache.insert(addr, make_coords(&[1, 2, 0]), 500);
339
340 assert_eq!(cache.len(), 1);
341 let coords = cache.get(&addr, 500).unwrap();
342 assert_eq!(coords.depth(), 2);
343 }
344
345 #[test]
346 fn test_coord_cache_eviction() {
347 let mut cache = CoordCache::new(2, 10000);
348
349 let addr1 = make_node_addr(1);
350 let addr2 = make_node_addr(2);
351 let addr3 = make_node_addr(3);
352
353 cache.insert(addr1, make_coords(&[1, 0]), 0);
354 cache.insert(addr2, make_coords(&[2, 0]), 100);
355
356 let _ = cache.get_and_touch(&addr2, 200);
358
359 cache.insert(addr3, make_coords(&[3, 0]), 300);
361
362 assert!(!cache.contains(&addr1, 300));
363 assert!(cache.contains(&addr2, 300));
364 assert!(cache.contains(&addr3, 300));
365 }
366
367 #[test]
368 fn test_coord_cache_evict_expired_first() {
369 let mut cache = CoordCache::new(2, 100);
370
371 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
372 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
373
374 cache.insert(make_node_addr(3), make_coords(&[3, 0]), 150);
376
377 assert!(!cache.contains(&make_node_addr(1), 150));
379 assert!(cache.contains(&make_node_addr(2), 150));
380 assert!(cache.contains(&make_node_addr(3), 150));
381 }
382
383 #[test]
384 fn test_coord_cache_purge_expired() {
385 let mut cache = CoordCache::new(100, 100);
386
387 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);
392
393 let purged = cache.purge_expired(151); assert_eq!(purged, 2);
397 assert_eq!(cache.len(), 1);
398 assert!(cache.contains(&make_node_addr(3), 151));
399 }
400
401 #[test]
402 fn test_coord_cache_stats() {
403 let mut cache = CoordCache::new(100, 100);
404
405 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
406 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
407
408 let stats = cache.stats(150);
409
410 assert_eq!(stats.entries, 2);
411 assert_eq!(stats.max_entries, 100);
412 assert_eq!(stats.expired, 1); assert!(stats.avg_age_ms > 0);
414 }
415
416 #[test]
417 fn test_coord_cache_insert_with_ttl() {
418 let mut cache = CoordCache::new(100, 1000);
419 let addr = make_node_addr(1);
420
421 cache.insert_with_ttl(addr, make_coords(&[1, 0]), 0, 200);
422
423 assert!(cache.contains(&addr, 100));
425 assert!(!cache.contains(&addr, 201));
426 }
427
428 #[test]
429 fn test_coord_cache_insert_with_ttl_update() {
430 let mut cache = CoordCache::new(100, 1000);
431 let addr = make_node_addr(1);
432
433 cache.insert_with_ttl(addr, make_coords(&[1, 0]), 0, 200);
434 cache.insert_with_ttl(addr, make_coords(&[1, 2, 0]), 100, 300);
435
436 assert_eq!(cache.len(), 1);
437 let coords = cache.get(&addr, 100).unwrap();
438 assert_eq!(coords.depth(), 2);
439 assert!(cache.contains(&addr, 399));
441 assert!(!cache.contains(&addr, 401));
442 }
443
444 #[test]
445 fn test_coord_cache_get_and_touch_removes_expired() {
446 let mut cache = CoordCache::new(100, 100);
447 let addr = make_node_addr(1);
448
449 cache.insert(addr, make_coords(&[1, 0]), 0);
450 assert_eq!(cache.len(), 1);
451
452 let result = cache.get_and_touch(&addr, 200);
454 assert!(result.is_none());
455 assert_eq!(cache.len(), 0);
457 }
458
459 #[test]
460 fn test_coord_cache_get_entry() {
461 let mut cache = CoordCache::new(100, 1000);
462 let addr = make_node_addr(1);
463
464 cache.insert(addr, make_coords(&[1, 0]), 500);
465
466 let entry = cache.get_entry(&addr).unwrap();
467 assert_eq!(entry.created_at(), 500);
468 assert_eq!(entry.expires_at(), 1500);
469
470 assert!(cache.get_entry(&make_node_addr(99)).is_none());
471 }
472
473 #[test]
474 fn test_coord_cache_remove() {
475 let mut cache = CoordCache::new(100, 1000);
476 let addr = make_node_addr(1);
477
478 cache.insert(addr, make_coords(&[1, 0]), 0);
479 assert_eq!(cache.len(), 1);
480
481 let removed = cache.remove(&addr);
482 assert!(removed.is_some());
483 assert_eq!(cache.len(), 0);
484
485 assert!(cache.remove(&addr).is_none());
487 }
488
489 #[test]
490 fn test_coord_cache_clear_and_is_empty() {
491 let mut cache = CoordCache::new(100, 1000);
492
493 assert!(cache.is_empty());
494
495 cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
496 cache.insert(make_node_addr(2), make_coords(&[2, 0]), 0);
497
498 assert!(!cache.is_empty());
499
500 cache.clear();
501 assert!(cache.is_empty());
502 assert_eq!(cache.len(), 0);
503 }
504
505 #[test]
506 fn test_coord_cache_invalidate_via_node_is_surgical() {
507 let mut cache = CoordCache::new(100, 1000);
508 let node = make_node_addr(1);
509 let downstream = make_node_addr(2);
510 let sibling = make_node_addr(3);
511
512 cache.insert(downstream, make_coords(&[2, 1, 0]), 0);
513 cache.insert(sibling, make_coords(&[3, 4, 0]), 0);
514
515 assert_eq!(cache.invalidate_via_node(&node), 1);
516 assert!(!cache.contains(&downstream, 0));
517 assert!(cache.contains(&sibling, 0));
518 }
519
520 #[test]
521 fn test_coord_cache_invalidate_other_roots_keeps_current_root() {
522 let mut cache = CoordCache::new(100, 1000);
523 let current_root = make_node_addr(0);
524 let current = make_node_addr(2);
525 let stale = make_node_addr(3);
526
527 cache.insert(current, make_coords(&[2, 1, 0]), 0);
528 cache.insert(stale, make_coords(&[3, 4, 9]), 0);
529
530 assert_eq!(cache.invalidate_other_roots(¤t_root), 1);
531 assert!(cache.contains(¤t, 0));
532 assert!(!cache.contains(&stale, 0));
533 }
534
535 #[test]
536 fn test_coord_cache_default() {
537 let cache = CoordCache::default();
538
539 assert_eq!(cache.max_entries(), DEFAULT_COORD_CACHE_SIZE);
540 assert_eq!(cache.default_ttl_ms(), DEFAULT_COORD_CACHE_TTL_MS);
541 assert!(cache.is_empty());
542 }
543
544 #[test]
545 fn test_coord_cache_set_default_ttl() {
546 let mut cache = CoordCache::new(100, 1000);
547 let addr = make_node_addr(1);
548
549 cache.set_default_ttl_ms(200);
550 assert_eq!(cache.default_ttl_ms(), 200);
551
552 cache.insert(addr, make_coords(&[1, 0]), 0);
553 assert!(cache.contains(&addr, 100));
555 assert!(!cache.contains(&addr, 201));
556 }
557
558 #[test]
559 fn test_coord_cache_stats_empty() {
560 let cache = CoordCache::new(100, 1000);
561 let stats = cache.stats(0);
562
563 assert_eq!(stats.entries, 0);
564 assert_eq!(stats.max_entries, 100);
565 assert_eq!(stats.expired, 0);
566 assert_eq!(stats.avg_age_ms, 0);
567 }
568}