1use std::{
4 collections::BTreeMap,
5 num::NonZeroUsize,
6 ops::{Bound, Range},
7 sync::Arc,
8};
9
10use bytes::Bytes;
11use parking_lot::Mutex;
12
13use crate::RangeError;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum CacheCapacity {
18 Bounded(NonZeroUsize),
20 Unbounded,
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum InsertOutcome {
27 Inserted,
29 AlreadyCovered,
31 TooLarge,
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
37pub struct Invalidation {
38 pub ranges: usize,
40 pub bytes: usize,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub struct CacheSnapshot {
47 pub capacity: CacheCapacity,
49 pub resident_bytes: usize,
51 pub keys: usize,
53 pub ranges: usize,
55 pub hits: u64,
57 pub partial_hits: u64,
59 pub misses: u64,
61 pub insertions: u64,
63 pub admissions_rejected_too_large: u64,
65 pub evictions: u64,
67}
68
69struct CacheBlock {
70 end: usize,
71 bytes: Bytes,
72 last_access: u64,
73}
74
75#[derive(Default)]
76struct Statistics {
77 hits: u64,
78 partial_hits: u64,
79 misses: u64,
80 insertions: u64,
81 admissions_rejected_too_large: u64,
82 evictions: u64,
83}
84
85enum EvictionPolicy<K> {
86 Unbounded,
87 Bounded {
88 lru: BTreeMap<u64, Box<LruEntry<K>>>,
91 next_access: u64,
92 },
93}
94
95struct LruEntry<K> {
96 key: K,
97 start: usize,
98}
99
100impl<K: Clone> EvictionPolicy<K> {
101 fn register(&mut self, key: &K, start: usize) -> u64 {
102 let Self::Bounded { lru, next_access } = self else {
103 return 0;
104 };
105 let access = *next_access;
106 *next_access = next_access
107 .checked_add(1)
108 .expect("range cache LRU clock exhausted");
109 assert!(
110 lru.insert(
111 access,
112 Box::new(LruEntry {
113 key: key.clone(),
114 start,
115 }),
116 )
117 .is_none(),
118 "new range has a unique LRU entry"
119 );
120 access
121 }
122}
123
124impl<K: Ord> EvictionPolicy<K> {
125 #[inline]
126 fn touch(&mut self, key: &K, start: usize, block: &mut CacheBlock) {
127 let Self::Bounded { lru, next_access } = self else {
128 return;
129 };
130 let previous = block.last_access;
131 let Some(resident) = lru.remove(&previous) else {
132 panic!("resident range must have an LRU entry");
133 };
134 debug_assert!(
135 &resident.key == key && resident.start == start,
136 "LRU entry must identify the resident range"
137 );
138
139 let access = *next_access;
140 *next_access = next_access
141 .checked_add(1)
142 .expect("range cache LRU clock exhausted");
143 block.last_access = access;
144 let replaced = lru.insert(access, resident);
145 debug_assert!(replaced.is_none(), "LRU access value is unique");
146 }
147}
148
149struct State<K> {
150 ranges: BTreeMap<K, BTreeMap<usize, CacheBlock>>,
151 eviction: EvictionPolicy<K>,
152 resident_bytes: usize,
153 resident_ranges: usize,
154 statistics: Statistics,
155}
156
157impl<K> State<K> {
158 fn new(capacity: CacheCapacity) -> Self {
159 Self {
160 ranges: BTreeMap::new(),
161 eviction: match capacity {
162 CacheCapacity::Bounded(_) => EvictionPolicy::Bounded {
163 lru: BTreeMap::new(),
164 next_access: 0,
165 },
166 CacheCapacity::Unbounded => EvictionPolicy::Unbounded,
167 },
168 resident_bytes: 0,
169 resident_ranges: 0,
170 statistics: Statistics::default(),
171 }
172 }
173}
174
175impl<K: Ord + Clone> State<K> {
176 #[inline]
177 fn touch(&mut self, key: &K, start: usize) {
178 if matches!(self.eviction, EvictionPolicy::Unbounded) {
179 return;
180 }
181 let Self {
182 ranges, eviction, ..
183 } = self;
184 let block = ranges
185 .get_mut(key)
186 .and_then(|ranges| ranges.get_mut(&start))
187 .expect("touched range remains resident");
188 eviction.touch(key, start, block);
189 }
190
191 fn take_block(&mut self, key: &K, start: usize) -> Option<CacheBlock> {
192 let (block, key_is_empty) = {
193 let ranges = self.ranges.get_mut(key)?;
194 let block = ranges.remove(&start)?;
195 (block, ranges.is_empty())
196 };
197
198 if key_is_empty {
199 self.ranges.remove(key);
200 }
201 self.resident_bytes = self
202 .resident_bytes
203 .checked_sub(block.bytes.len())
204 .expect("resident byte accounting cannot underflow");
205 self.resident_ranges = self
206 .resident_ranges
207 .checked_sub(1)
208 .expect("resident range accounting cannot underflow");
209 Some(block)
210 }
211
212 fn remove(&mut self, key: &K, start: usize) -> Option<CacheBlock> {
213 let block = self.take_block(key, start)?;
214 if let EvictionPolicy::Bounded { lru, .. } = &mut self.eviction {
215 let Some(resident) = lru.remove(&block.last_access) else {
216 panic!("removed range must have an LRU entry");
217 };
218 assert!(
219 &resident.key == key && resident.start == start,
220 "LRU entry must identify the removed range"
221 );
222 }
223 Some(block)
224 }
225
226 fn evict_oldest(&mut self) -> CacheBlock {
227 let EvictionPolicy::Bounded { lru, .. } = &mut self.eviction else {
228 panic!("only bounded caches evict");
229 };
230 let Some((access, resident)) = lru.pop_first() else {
231 panic!("resident bytes require an LRU entry");
232 };
233 let LruEntry { key, start } = *resident;
234 let block = self
235 .take_block(&key, start)
236 .expect("LRU range remains resident");
237 assert_eq!(
238 block.last_access, access,
239 "evicted range access value matches its LRU entry"
240 );
241 block
242 }
243}
244
245pub struct RangeCache<K> {
247 inner: Arc<Mutex<State<K>>>,
248 capacity: CacheCapacity,
249}
250
251impl<K> Clone for RangeCache<K> {
252 fn clone(&self) -> Self {
253 Self {
254 inner: Arc::clone(&self.inner),
255 capacity: self.capacity,
256 }
257 }
258}
259
260impl<K> RangeCache<K> {
261 #[must_use]
263 pub fn new(capacity: CacheCapacity) -> Self {
264 Self {
265 inner: Arc::new(Mutex::new(State::new(capacity))),
266 capacity,
267 }
268 }
269
270 #[must_use]
272 pub const fn capacity(&self) -> CacheCapacity {
273 self.capacity
274 }
275}
276
277impl<K: Ord + Clone> RangeCache<K> {
278 pub fn get(&self, key: &K, range: Range<usize>) -> Result<Option<Bytes>, RangeError> {
287 validate_range(&range)?;
288 let mut state = self.inner.lock();
289
290 if range.is_empty() {
291 state.statistics.hits += 1;
292 return Ok(Some(Bytes::new()));
293 }
294
295 let hit = state.ranges.get(key).and_then(|ranges| {
296 ranges
297 .range(..=range.start)
298 .next_back()
299 .filter(|(_, block)| range.end <= block.end)
300 .map(|(&start, block)| {
301 let offset = range.start - start;
302 (start, block.bytes.slice(offset..offset + range.len()))
303 })
304 });
305 if let Some((start, bytes)) = hit {
306 state.statistics.hits += 1;
307 state.touch(key, start);
308 return Ok(Some(bytes));
309 }
310
311 let mut has_coverage = false;
312 {
313 let State {
314 ranges, eviction, ..
315 } = &mut *state;
316 if let Some(ranges) = ranges.get_mut(key) {
317 if let Some((&start, block)) = ranges
318 .range_mut(..=range.start)
319 .next_back()
320 .filter(|(_, block)| block.end > range.start)
321 {
322 has_coverage = true;
323 eviction.touch(key, start, block);
324 }
325 for (&start, block) in
326 ranges.range_mut((Bound::Excluded(range.start), Bound::Excluded(range.end)))
327 {
328 has_coverage = true;
329 eviction.touch(key, start, block);
330 }
331 }
332 }
333 if has_coverage {
334 state.statistics.partial_hits += 1;
335 } else {
336 state.statistics.misses += 1;
337 }
338 Ok(None)
339 }
340
341 pub fn missing_ranges(
347 &self,
348 key: &K,
349 range: Range<usize>,
350 ) -> Result<Vec<Range<usize>>, RangeError> {
351 validate_range(&range)?;
352 if range.is_empty() {
353 return Ok(Vec::new());
354 }
355
356 let state = self.inner.lock();
357 let Some(ranges) = state.ranges.get(key) else {
358 return Ok(vec![range]);
359 };
360
361 let mut missing = Vec::new();
362 let mut cursor = range.start;
363 if let Some((_, block)) = ranges
364 .range(..=range.start)
365 .next_back()
366 .filter(|(_, block)| block.end > range.start)
367 {
368 cursor = cursor.max(block.end.min(range.end));
369 }
370
371 for (&start, block) in
372 ranges.range((Bound::Excluded(range.start), Bound::Excluded(range.end)))
373 {
374 if cursor < start {
375 missing.push(cursor..start);
376 }
377 cursor = cursor.max(block.end.min(range.end));
378 if cursor == range.end {
379 break;
380 }
381 }
382 if cursor < range.end {
383 missing.push(cursor..range.end);
384 }
385 Ok(missing)
386 }
387
388 pub fn insert(
405 &self,
406 key: K,
407 range: Range<usize>,
408 bytes: Bytes,
409 ) -> Result<InsertOutcome, RangeError> {
410 validate_range(&range)?;
411 let expected = range.len();
412 if bytes.len() != expected {
413 return Err(RangeError::PayloadLengthMismatch {
414 range,
415 expected,
416 actual: bytes.len(),
417 });
418 }
419 if range.is_empty() {
420 return Ok(InsertOutcome::AlreadyCovered);
421 }
422
423 let mut state = self.inner.lock();
424 let containing = state.ranges.get(&key).and_then(|ranges| {
425 ranges
426 .range(..=range.start)
427 .next_back()
428 .filter(|(_, block)| range.end <= block.end)
429 });
430 if containing.is_some() {
431 return Ok(InsertOutcome::AlreadyCovered);
432 }
433
434 let mut merged_start = range.start;
435 let mut merged_end = range.end;
436 let mut affected = Vec::new();
437 if let Some(ranges) = state.ranges.get(&key) {
438 let following = match ranges.range(..=range.start).next_back() {
439 Some((&start, block)) if block.end >= range.start => {
440 merged_start = start;
441 merged_end = merged_end.max(block.end);
442 affected.push((start, block.end));
443 Bound::Excluded(start)
444 }
445 Some(_) | None => Bound::Included(range.start),
446 };
447
448 for (&start, block) in ranges.range((following, Bound::Unbounded)) {
449 if start > merged_end {
450 break;
451 }
452 merged_end = merged_end.max(block.end);
453 affected.push((start, block.end));
454 }
455 }
456
457 let merged_length = merged_end - merged_start;
458 match self.capacity {
459 CacheCapacity::Bounded(capacity) if merged_length > capacity.get() => {
460 state.statistics.admissions_rejected_too_large += 1;
461 return Ok(InsertOutcome::TooLarge);
462 }
463 CacheCapacity::Bounded(_) | CacheCapacity::Unbounded => {}
464 }
465
466 let merged_bytes =
467 if affected.is_empty() || (merged_start == range.start && merged_end == range.end) {
468 bytes
469 } else {
470 let mut merged = vec![0; merged_length];
471 for &(start, end) in &affected {
472 let cached = &state.ranges[&key][&start].bytes;
473 let offset = start - merged_start;
474 merged[offset..offset + (end - start)].copy_from_slice(cached);
475 }
476 let offset = range.start - merged_start;
477 merged[offset..offset + expected].copy_from_slice(&bytes);
478 Bytes::from(merged)
479 };
480
481 for (start, _) in affected {
482 state
483 .remove(&key, start)
484 .expect("affected range remains resident");
485 }
486
487 let access = state.eviction.register(&key, merged_start);
488 let previous = state.ranges.entry(key).or_default().insert(
489 merged_start,
490 CacheBlock {
491 end: merged_end,
492 bytes: merged_bytes,
493 last_access: access,
494 },
495 );
496 assert!(previous.is_none(), "merged range start must be vacant");
497 state.resident_bytes += merged_length;
498 state.resident_ranges += 1;
499 state.statistics.insertions += 1;
500
501 if let CacheCapacity::Bounded(capacity) = self.capacity {
502 while state.resident_bytes > capacity.get() {
503 let _ = state.evict_oldest();
504 state.statistics.evictions += 1;
505 }
506 }
507
508 Ok(InsertOutcome::Inserted)
509 }
510
511 #[must_use]
518 pub fn invalidate(&self, key: &K) -> Invalidation {
519 let mut state = self.inner.lock();
520 let starts = state
521 .ranges
522 .get(key)
523 .map(|ranges| ranges.keys().copied().collect::<Vec<_>>())
524 .unwrap_or_default();
525 let mut invalidation = Invalidation::default();
526 for start in starts {
527 let block = state
528 .remove(key, start)
529 .expect("invalidated range remains resident");
530 invalidation.ranges += 1;
531 invalidation.bytes += block.bytes.len();
532 }
533 invalidation
534 }
535
536 #[must_use]
538 pub fn clear(&self) -> Invalidation {
539 let mut state = self.inner.lock();
540 let invalidation = Invalidation {
541 ranges: state.resident_ranges,
542 bytes: state.resident_bytes,
543 };
544 state.ranges.clear();
545 if let EvictionPolicy::Bounded { lru, .. } = &mut state.eviction {
546 lru.clear();
547 }
548 state.resident_bytes = 0;
549 state.resident_ranges = 0;
550 invalidation
551 }
552
553 #[must_use]
555 pub fn snapshot(&self) -> CacheSnapshot {
556 let state = self.inner.lock();
557 CacheSnapshot {
558 capacity: self.capacity,
559 resident_bytes: state.resident_bytes,
560 keys: state.ranges.len(),
561 ranges: state.resident_ranges,
562 hits: state.statistics.hits,
563 partial_hits: state.statistics.partial_hits,
564 misses: state.statistics.misses,
565 insertions: state.statistics.insertions,
566 admissions_rejected_too_large: state.statistics.admissions_rejected_too_large,
567 evictions: state.statistics.evictions,
568 }
569 }
570
571 #[cfg(feature = "async")]
572 pub(crate) fn read_plan(&self, key: &K, range: Range<usize>) -> Result<ReadPlan, RangeError> {
573 validate_range(&range)?;
574 let mut state = self.inner.lock();
575 if range.is_empty() {
576 state.statistics.hits += 1;
577 return Ok(ReadPlan::Complete(Bytes::new()));
578 }
579
580 let hit = state.ranges.get(key).and_then(|ranges| {
581 ranges
582 .range(..=range.start)
583 .next_back()
584 .filter(|(_, block)| range.end <= block.end)
585 .map(|(&start, block)| {
586 let offset = range.start - start;
587 (start, block.bytes.slice(offset..offset + range.len()))
588 })
589 });
590 if let Some((start, bytes)) = hit {
591 state.statistics.hits += 1;
592 state.touch(key, start);
593 return Ok(ReadPlan::Complete(bytes));
594 }
595
596 let mut cached = Vec::new();
597 let mut missing = Vec::new();
598 let mut has_coverage = false;
599 let mut cursor = range.start;
600 {
601 let State {
602 ranges, eviction, ..
603 } = &mut *state;
604 if let Some(ranges) = ranges.get_mut(key) {
605 if let Some((&start, block)) = ranges
606 .range_mut(..=range.start)
607 .next_back()
608 .filter(|(_, block)| block.end > range.start)
609 {
610 let covered_end = block.end.min(range.end);
611 let offset = range.start - start;
612 cached.push((
613 range.start..covered_end,
614 block
615 .bytes
616 .slice(offset..offset + covered_end - range.start),
617 ));
618 has_coverage = true;
619 cursor = cursor.max(covered_end);
620 eviction.touch(key, start, block);
621 }
622
623 for (&start, block) in
624 ranges.range_mut((Bound::Excluded(range.start), Bound::Excluded(range.end)))
625 {
626 if cursor < start {
627 missing.push(cursor..start);
628 }
629 let covered_end = block.end.min(range.end);
630 cached.push((start..covered_end, block.bytes.slice(..covered_end - start)));
631 has_coverage = true;
632 cursor = cursor.max(covered_end);
633 eviction.touch(key, start, block);
634 if cursor == range.end {
635 break;
636 }
637 }
638 }
639 }
640 if cursor < range.end {
641 missing.push(cursor..range.end);
642 }
643
644 if has_coverage {
645 state.statistics.partial_hits += 1;
646 } else {
647 state.statistics.misses += 1;
648 }
649 Ok(ReadPlan::Fetch { cached, missing })
650 }
651}
652
653fn validate_range(range: &Range<usize>) -> Result<(), RangeError> {
654 if range.start > range.end {
655 return Err(RangeError::ReversedRange {
656 start: range.start,
657 end: range.end,
658 });
659 }
660 Ok(())
661}
662
663#[cfg(feature = "async")]
664pub(crate) enum ReadPlan {
665 Complete(Bytes),
666 Fetch {
667 cached: Vec<(Range<usize>, Bytes)>,
668 missing: Vec<Range<usize>>,
669 },
670}
671
672#[cfg(test)]
673mod tests {
674 use std::{collections::BTreeMap, num::NonZeroUsize, sync::Arc};
675
676 use bytes::Bytes;
677 use parking_lot::Mutex;
678
679 use super::{CacheBlock, CacheCapacity, EvictionPolicy, RangeCache, State};
680
681 fn bounded_state() -> State<&'static str> {
682 State::new(CacheCapacity::Bounded(
683 NonZeroUsize::new(8).expect("test capacity is non-zero"),
684 ))
685 }
686
687 fn add_untracked_block(state: &mut State<&'static str>) {
688 state.ranges.entry("key").or_default().insert(
689 0,
690 CacheBlock {
691 end: 1,
692 bytes: Bytes::from_static(b"x"),
693 last_access: 0,
694 },
695 );
696 state.resident_bytes = 1;
697 state.resident_ranges = 1;
698 }
699
700 #[test]
701 #[should_panic(expected = "resident range must have an LRU entry")]
702 fn touching_an_untracked_bounded_range_panics() {
703 let mut state = bounded_state();
704 add_untracked_block(&mut state);
705 state.touch(&"key", 0);
706 }
707
708 #[test]
709 #[should_panic(expected = "removed range must have an LRU entry")]
710 fn removing_an_untracked_bounded_range_panics() {
711 let mut state = bounded_state();
712 add_untracked_block(&mut state);
713 let _ = state.remove(&"key", 0);
714 }
715
716 #[test]
717 #[should_panic(expected = "only bounded caches evict")]
718 fn unbounded_state_cannot_evict() {
719 let mut state = State::<u8>::new(CacheCapacity::Unbounded);
720 let _ = state.evict_oldest();
721 }
722
723 #[test]
724 #[should_panic(expected = "resident bytes require an LRU entry")]
725 fn empty_bounded_state_cannot_evict() {
726 let mut state = State::<u8>::new(CacheCapacity::Bounded(
727 NonZeroUsize::new(1).expect("test capacity is non-zero"),
728 ));
729 let _ = state.evict_oldest();
730 }
731
732 #[test]
733 #[should_panic(expected = "range cache LRU clock exhausted")]
734 fn registering_after_lru_clock_exhaustion_panics() {
735 let mut eviction = EvictionPolicy::Bounded {
736 lru: BTreeMap::default(),
737 next_access: u64::MAX,
738 };
739 let _ = eviction.register(&"key", 0);
740 }
741
742 #[test]
743 fn absent_internal_ranges_are_noops() {
744 let mut state = State::<&str>::new(CacheCapacity::Unbounded);
745 let _ = state.take_block(&"missing", 0);
746 state.ranges.insert("empty", BTreeMap::default());
747 let _ = state.take_block(&"empty", 0);
748 let _ = state.remove(&"missing", 0);
749 assert_eq!(state.resident_bytes, 0);
750 assert_eq!(state.resident_ranges, 0);
751 }
752
753 #[test]
754 fn overlapping_internal_ranges_do_not_create_negative_gaps() {
755 let mut state = State::new(CacheCapacity::Unbounded);
756 state.ranges.entry("key").or_default().insert(
757 0,
758 CacheBlock {
759 end: 4,
760 bytes: Bytes::from_static(b"abcd"),
761 last_access: 0,
762 },
763 );
764 state.ranges.entry("key").or_default().insert(
765 2,
766 CacheBlock {
767 end: 6,
768 bytes: Bytes::from_static(b"cdef"),
769 last_access: 0,
770 },
771 );
772 state.resident_bytes = 8;
773 state.resident_ranges = 2;
774 let cache = RangeCache {
775 inner: Arc::new(Mutex::new(state)),
776 capacity: CacheCapacity::Unbounded,
777 };
778
779 assert_eq!(
780 cache.missing_ranges(&"key", 0..6).expect("valid range"),
781 Vec::<std::ops::Range<usize>>::new()
782 );
783 #[cfg(feature = "async")]
784 let _ = cache.read_plan(&"key", 0..6).expect("valid read plan");
785 }
786}