Skip to main content

range_cache/
cache.rs

1//! Core cache types.
2
3use std::{
4    collections::{BTreeMap, BTreeSet},
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/// The maximum number of payload bytes resident in a cache.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum CacheCapacity {
18    /// Evict least-recently-used ranges to enforce the given byte ceiling.
19    Bounded(NonZeroUsize),
20    /// Retain all admitted ranges until explicit invalidation.
21    Unbounded,
22}
23
24/// The result of an insertion attempt.
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum InsertOutcome {
27    /// The bytes were admitted to the cache.
28    Inserted,
29    /// Existing coverage already fully contained the inserted range.
30    AlreadyCovered,
31    /// The resulting merged range exceeded the bounded capacity.
32    TooLarge,
33}
34
35/// The effect of invalidating cache state.
36#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
37pub struct Invalidation {
38    /// Number of ranges removed.
39    pub ranges: usize,
40    /// Number of payload bytes removed.
41    pub bytes: usize,
42}
43
44/// A point-in-time cache state and statistics snapshot.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub struct CacheSnapshot {
47    /// Configured capacity.
48    pub capacity: CacheCapacity,
49    /// Total resident payload bytes.
50    pub resident_bytes: usize,
51    /// Number of keys with resident ranges.
52    pub keys: usize,
53    /// Number of resident ranges.
54    pub ranges: usize,
55    /// Fully cached reads.
56    pub hits: u64,
57    /// Reads with some, but not all, requested bytes cached.
58    pub partial_hits: u64,
59    /// Reads with none of the requested bytes cached.
60    pub misses: u64,
61    /// Successfully admitted insertions.
62    pub insertions: u64,
63    /// Insertions rejected because the merged range was too large.
64    pub admissions_rejected_too_large: u64,
65    /// Ranges removed by capacity enforcement.
66    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
85struct State<K> {
86    ranges: BTreeMap<K, BTreeMap<usize, CacheBlock>>,
87    lru: BTreeSet<(u64, K, usize)>,
88    resident_bytes: usize,
89    next_access: u64,
90    statistics: Statistics,
91}
92
93impl<K> Default for State<K> {
94    fn default() -> Self {
95        Self {
96            ranges: BTreeMap::new(),
97            lru: BTreeSet::new(),
98            resident_bytes: 0,
99            next_access: 0,
100            statistics: Statistics::default(),
101        }
102    }
103}
104
105impl<K: Ord + Clone> State<K> {
106    fn take_access(&mut self) -> u64 {
107        let access = self.next_access;
108        self.next_access = self
109            .next_access
110            .checked_add(1)
111            .expect("range cache LRU clock exhausted");
112        access
113    }
114
115    fn touch(&mut self, key: &K, start: usize) {
116        let Some(previous) = self
117            .ranges
118            .get(key)
119            .and_then(|ranges| ranges.get(&start))
120            .map(|block| block.last_access)
121        else {
122            return;
123        };
124
125        assert!(
126            self.lru.remove(&(previous, key.clone(), start)),
127            "resident range must have an LRU entry"
128        );
129        let next = self.take_access();
130        self.ranges
131            .get_mut(key)
132            .and_then(|ranges| ranges.get_mut(&start))
133            .expect("touched range remains resident")
134            .last_access = next;
135        assert!(
136            self.lru.insert((next, key.clone(), start)),
137            "LRU access value is unique"
138        );
139    }
140
141    fn remove(&mut self, key: &K, start: usize) -> Option<CacheBlock> {
142        let (block, key_is_empty) = {
143            let ranges = self.ranges.get_mut(key)?;
144            let block = ranges.remove(&start)?;
145            (block, ranges.is_empty())
146        };
147
148        if key_is_empty {
149            self.ranges.remove(key);
150        }
151        assert!(
152            self.lru.remove(&(block.last_access, key.clone(), start)),
153            "removed range must have an LRU entry"
154        );
155        self.resident_bytes = self
156            .resident_bytes
157            .checked_sub(block.bytes.len())
158            .expect("resident byte accounting cannot underflow");
159        Some(block)
160    }
161
162    fn covered(&self, key: &K, requested: &Range<usize>) -> Vec<(usize, Range<usize>)> {
163        let Some(ranges) = self.ranges.get(key) else {
164            return Vec::new();
165        };
166
167        let mut covered = Vec::new();
168        match ranges.range(..=requested.start).next_back() {
169            Some((&start, block)) if block.end > requested.start => {
170                covered.push((start, requested.start..block.end.min(requested.end)));
171            }
172            Some(_) | None => {}
173        }
174
175        covered.extend(
176            ranges
177                .range((
178                    Bound::Excluded(requested.start),
179                    Bound::Excluded(requested.end),
180                ))
181                .map(|(&start, block)| {
182                    (
183                        start,
184                        start.max(requested.start)..block.end.min(requested.end),
185                    )
186                }),
187        );
188        covered
189    }
190}
191
192/// A cloneable, thread-safe sparse cache of byte ranges keyed by `K`.
193pub struct RangeCache<K> {
194    inner: Arc<Mutex<State<K>>>,
195    capacity: CacheCapacity,
196}
197
198impl<K> Clone for RangeCache<K> {
199    fn clone(&self) -> Self {
200        Self {
201            inner: Arc::clone(&self.inner),
202            capacity: self.capacity,
203        }
204    }
205}
206
207impl<K> RangeCache<K> {
208    /// Creates an empty cache with an explicit capacity policy.
209    #[must_use]
210    pub fn new(capacity: CacheCapacity) -> Self {
211        Self {
212            inner: Arc::new(Mutex::new(State::default())),
213            capacity,
214        }
215    }
216
217    /// Returns the configured capacity policy.
218    #[must_use]
219    pub const fn capacity(&self) -> CacheCapacity {
220        self.capacity
221    }
222}
223
224impl<K: Ord + Clone> RangeCache<K> {
225    /// Returns the requested bytes when the entire range is cached.
226    ///
227    /// A hit contained in one resident range returns a zero-copy [`Bytes`]
228    /// slice. Empty ranges always succeed.
229    ///
230    /// # Errors
231    ///
232    /// Returns [`RangeError::ReversedRange`] when `range.end < range.start`.
233    pub fn get(&self, key: &K, range: Range<usize>) -> Result<Option<Bytes>, RangeError> {
234        validate_range(&range)?;
235        let mut state = self.inner.lock();
236
237        if range.is_empty() {
238            state.statistics.hits += 1;
239            return Ok(Some(Bytes::new()));
240        }
241
242        let hit = state.ranges.get(key).and_then(|ranges| {
243            ranges
244                .range(..=range.start)
245                .next_back()
246                .filter(|(_, block)| range.end <= block.end)
247                .map(|(&start, block)| {
248                    let offset = range.start - start;
249                    (start, block.bytes.slice(offset..offset + range.len()))
250                })
251        });
252
253        if let Some((start, bytes)) = hit {
254            state.statistics.hits += 1;
255            state.touch(key, start);
256            return Ok(Some(bytes));
257        }
258
259        let covered = state.covered(key, &range);
260        if covered.is_empty() {
261            state.statistics.misses += 1;
262        } else {
263            state.statistics.partial_hits += 1;
264            for (start, _) in covered {
265                state.touch(key, start);
266            }
267        }
268        Ok(None)
269    }
270
271    /// Returns the gaps within `range` that are not resident for `key`.
272    ///
273    /// # Errors
274    ///
275    /// Returns [`RangeError::ReversedRange`] when `range.end < range.start`.
276    pub fn missing_ranges(
277        &self,
278        key: &K,
279        range: Range<usize>,
280    ) -> Result<Vec<Range<usize>>, RangeError> {
281        validate_range(&range)?;
282        if range.is_empty() {
283            return Ok(Vec::new());
284        }
285
286        let state = self.inner.lock();
287        let covered = state.covered(key, &range);
288        Ok(missing_ranges(&range, &covered))
289    }
290
291    /// Inserts `bytes` for `range`, merging adjacent and overlapping ranges.
292    ///
293    /// An insert wholly contained by one existing range is ignored. Otherwise,
294    /// inserted bytes replace overlapping bytes while cached prefix and suffix
295    /// bytes remain intact.
296    ///
297    /// # Errors
298    ///
299    /// Returns [`RangeError::ReversedRange`] for a reversed range or
300    /// [`RangeError::PayloadLengthMismatch`] when the payload length differs
301    /// from the range length.
302    ///
303    /// # Panics
304    ///
305    /// Panics only if an internal range-map, LRU, or byte-accounting invariant
306    /// has been violated.
307    pub fn insert(
308        &self,
309        key: K,
310        range: Range<usize>,
311        bytes: Bytes,
312    ) -> Result<InsertOutcome, RangeError> {
313        validate_range(&range)?;
314        let expected = range.len();
315        if bytes.len() != expected {
316            return Err(RangeError::PayloadLengthMismatch {
317                range,
318                expected,
319                actual: bytes.len(),
320            });
321        }
322        if range.is_empty() {
323            return Ok(InsertOutcome::AlreadyCovered);
324        }
325
326        let mut state = self.inner.lock();
327        let containing = state.ranges.get(&key).and_then(|ranges| {
328            ranges
329                .range(..=range.start)
330                .next_back()
331                .filter(|(_, block)| range.end <= block.end)
332        });
333        if containing.is_some() {
334            return Ok(InsertOutcome::AlreadyCovered);
335        }
336
337        let mut merged_start = range.start;
338        let mut merged_end = range.end;
339        let mut affected = Vec::new();
340        if let Some(ranges) = state.ranges.get(&key) {
341            for (&start, block) in ranges {
342                if block.end < merged_start {
343                    continue;
344                }
345                if start > merged_end {
346                    break;
347                }
348                merged_start = merged_start.min(start);
349                merged_end = merged_end.max(block.end);
350                affected.push((start, block.end, block.bytes.clone()));
351            }
352        }
353
354        let merged_length = merged_end - merged_start;
355        match self.capacity {
356            CacheCapacity::Bounded(capacity) if merged_length > capacity.get() => {
357                state.statistics.admissions_rejected_too_large += 1;
358                return Ok(InsertOutcome::TooLarge);
359            }
360            CacheCapacity::Bounded(_) | CacheCapacity::Unbounded => {}
361        }
362
363        let merged_bytes =
364            if affected.is_empty() || (merged_start == range.start && merged_end == range.end) {
365                bytes
366            } else {
367                let mut merged = vec![0; merged_length];
368                for (start, end, cached) in &affected {
369                    let offset = start - merged_start;
370                    merged[offset..offset + (end - start)].copy_from_slice(cached);
371                }
372                let offset = range.start - merged_start;
373                merged[offset..offset + expected].copy_from_slice(&bytes);
374                Bytes::from(merged)
375            };
376
377        for (start, _, _) in affected {
378            state
379                .remove(&key, start)
380                .expect("affected range remains resident");
381        }
382
383        let access = state.take_access();
384        assert!(
385            state.lru.insert((access, key.clone(), merged_start)),
386            "new range has a unique LRU entry"
387        );
388        let previous = state.ranges.entry(key).or_default().insert(
389            merged_start,
390            CacheBlock {
391                end: merged_end,
392                bytes: merged_bytes,
393                last_access: access,
394            },
395        );
396        assert!(previous.is_none(), "merged range start must be vacant");
397        state.resident_bytes += merged_length;
398        state.statistics.insertions += 1;
399
400        if let CacheCapacity::Bounded(capacity) = self.capacity {
401            while state.resident_bytes > capacity.get() {
402                let (_, oldest_key, oldest_start) = state
403                    .lru
404                    .first()
405                    .cloned()
406                    .expect("resident bytes require an LRU entry");
407                state
408                    .remove(&oldest_key, oldest_start)
409                    .expect("LRU range remains resident");
410                state.statistics.evictions += 1;
411            }
412        }
413
414        Ok(InsertOutcome::Inserted)
415    }
416
417    /// Removes all ranges associated with `key`.
418    ///
419    /// # Panics
420    ///
421    /// Panics only if an internal range-map, LRU, or byte-accounting invariant
422    /// has been violated.
423    #[must_use]
424    pub fn invalidate(&self, key: &K) -> Invalidation {
425        let mut state = self.inner.lock();
426        let starts = state
427            .ranges
428            .get(key)
429            .map(|ranges| ranges.keys().copied().collect::<Vec<_>>())
430            .unwrap_or_default();
431        let mut invalidation = Invalidation::default();
432        for start in starts {
433            let block = state
434                .remove(key, start)
435                .expect("invalidated range remains resident");
436            invalidation.ranges += 1;
437            invalidation.bytes += block.bytes.len();
438        }
439        invalidation
440    }
441
442    /// Removes every resident range while retaining accumulated statistics.
443    #[must_use]
444    pub fn clear(&self) -> Invalidation {
445        let mut state = self.inner.lock();
446        let invalidation = Invalidation {
447            ranges: state.lru.len(),
448            bytes: state.resident_bytes,
449        };
450        state.ranges.clear();
451        state.lru.clear();
452        state.resident_bytes = 0;
453        invalidation
454    }
455
456    /// Returns a consistent snapshot of cache state and lifetime statistics.
457    #[must_use]
458    pub fn snapshot(&self) -> CacheSnapshot {
459        let state = self.inner.lock();
460        CacheSnapshot {
461            capacity: self.capacity,
462            resident_bytes: state.resident_bytes,
463            keys: state.ranges.len(),
464            ranges: state.lru.len(),
465            hits: state.statistics.hits,
466            partial_hits: state.statistics.partial_hits,
467            misses: state.statistics.misses,
468            insertions: state.statistics.insertions,
469            admissions_rejected_too_large: state.statistics.admissions_rejected_too_large,
470            evictions: state.statistics.evictions,
471        }
472    }
473
474    #[cfg(feature = "async")]
475    pub(crate) fn read_plan(&self, key: &K, range: Range<usize>) -> Result<ReadPlan, RangeError> {
476        validate_range(&range)?;
477        let mut state = self.inner.lock();
478        if range.is_empty() {
479            state.statistics.hits += 1;
480            return Ok(ReadPlan::Complete(Bytes::new()));
481        }
482
483        let hit = state.ranges.get(key).and_then(|ranges| {
484            ranges
485                .range(..=range.start)
486                .next_back()
487                .filter(|(_, block)| range.end <= block.end)
488                .map(|(&start, block)| {
489                    let offset = range.start - start;
490                    (start, block.bytes.slice(offset..offset + range.len()))
491                })
492        });
493        if let Some((start, bytes)) = hit {
494            state.statistics.hits += 1;
495            state.touch(key, start);
496            return Ok(ReadPlan::Complete(bytes));
497        }
498
499        let covered = state.covered(key, &range);
500        let missing = missing_ranges(&range, &covered);
501        let cached = covered
502            .iter()
503            .map(|(start, covered_range)| {
504                let block = state
505                    .ranges
506                    .get(key)
507                    .and_then(|ranges| ranges.get(start))
508                    .expect("covered range remains resident");
509                let offset = covered_range.start - start;
510                (
511                    covered_range.clone(),
512                    block.bytes.slice(offset..offset + covered_range.len()),
513                )
514            })
515            .collect();
516
517        if covered.is_empty() {
518            state.statistics.misses += 1;
519        } else {
520            state.statistics.partial_hits += 1;
521            for (start, _) in covered {
522                state.touch(key, start);
523            }
524        }
525        Ok(ReadPlan::Fetch { cached, missing })
526    }
527}
528
529fn validate_range(range: &Range<usize>) -> Result<(), RangeError> {
530    if range.start > range.end {
531        return Err(RangeError::ReversedRange {
532            start: range.start,
533            end: range.end,
534        });
535    }
536    Ok(())
537}
538
539fn missing_ranges(
540    requested: &Range<usize>,
541    covered: &[(usize, Range<usize>)],
542) -> Vec<Range<usize>> {
543    let mut missing = Vec::new();
544    let mut cursor = requested.start;
545    for (_, range) in covered {
546        if cursor < range.start {
547            missing.push(cursor..range.start);
548        }
549        cursor = cursor.max(range.end);
550    }
551    if cursor < requested.end {
552        missing.push(cursor..requested.end);
553    }
554    missing
555}
556
557#[cfg(feature = "async")]
558pub(crate) enum ReadPlan {
559    Complete(Bytes),
560    Fetch {
561        cached: Vec<(Range<usize>, Bytes)>,
562        missing: Vec<Range<usize>>,
563    },
564}