gix_pack/cache/
object.rs

1//! This module is a bit 'misplaced' if spelled out like '`gix_pack::cache::object::`*' but is best placed here for code reuse and
2//! general usefulness.
3use crate::cache;
4
5#[cfg(feature = "object-cache-dynamic")]
6mod memory {
7    use std::num::NonZeroUsize;
8
9    use clru::WeightScale;
10
11    use crate::{cache, cache::set_vec_to_slice};
12
13    struct Entry {
14        data: Vec<u8>,
15        kind: gix_object::Kind,
16    }
17
18    type Key = gix_hash::ObjectId;
19
20    struct CustomScale;
21
22    impl WeightScale<Key, Entry> for CustomScale {
23        fn weight(&self, key: &Key, value: &Entry) -> usize {
24            value.data.len() + std::mem::size_of::<Entry>() + key.as_bytes().len()
25        }
26    }
27
28    /// An LRU cache with hash map backing and an eviction rule based on the memory usage for object data in bytes.
29    pub struct MemoryCappedHashmap {
30        inner: clru::CLruCache<Key, Entry, gix_hashtable::hash::Builder, CustomScale>,
31        free_list: Vec<Vec<u8>>,
32        debug: gix_features::cache::Debug,
33    }
34
35    impl MemoryCappedHashmap {
36        /// The amount of bytes we can hold in total, or the value we saw in `new(…)`.
37        pub fn capacity(&self) -> usize {
38            self.inner.capacity()
39        }
40        /// Return a new instance which evicts least recently used items if it uses more than `memory_cap_in_bytes`
41        /// object data.
42        pub fn new(memory_cap_in_bytes: usize) -> MemoryCappedHashmap {
43            MemoryCappedHashmap {
44                inner: clru::CLruCache::with_config(
45                    clru::CLruCacheConfig::new(NonZeroUsize::new(memory_cap_in_bytes).expect("non zero"))
46                        .with_hasher(gix_hashtable::hash::Builder)
47                        .with_scale(CustomScale),
48                ),
49                free_list: Vec::new(),
50                debug: gix_features::cache::Debug::new(format!("MemoryCappedObjectHashmap({memory_cap_in_bytes}B)")),
51            }
52        }
53    }
54
55    impl cache::Object for MemoryCappedHashmap {
56        /// Put the object going by `id` of `kind` with `data` into the cache.
57        fn put(&mut self, id: gix_hash::ObjectId, kind: gix_object::Kind, data: &[u8]) {
58            self.debug.put();
59            let Some(data) = set_vec_to_slice(self.free_list.pop().unwrap_or_default(), data) else {
60                return;
61            };
62            let res = self.inner.put_with_weight(id, Entry { data, kind });
63            match res {
64                Ok(Some(previous_entry)) => self.free_list.push(previous_entry.data),
65                Ok(None) => {}
66                Err((_key, value)) => self.free_list.push(value.data),
67            }
68        }
69
70        /// Try to retrieve the object named `id` and place its data into `out` if available and return `Some(kind)` if found.
71        fn get(&mut self, id: &gix_hash::ObjectId, out: &mut Vec<u8>) -> Option<gix_object::Kind> {
72            let res = self.inner.get(id).and_then(|e| {
73                set_vec_to_slice(out, &e.data)?;
74                Some(e.kind)
75            });
76            if res.is_some() {
77                self.debug.hit();
78            } else {
79                self.debug.miss();
80            }
81            res
82        }
83    }
84}
85#[cfg(feature = "object-cache-dynamic")]
86pub use memory::MemoryCappedHashmap;
87
88/// A cache implementation that doesn't do any caching.
89pub struct Never;
90
91impl cache::Object for Never {
92    /// Noop
93    fn put(&mut self, _id: gix_hash::ObjectId, _kind: gix_object::Kind, _data: &[u8]) {}
94
95    /// Noop
96    fn get(&mut self, _id: &gix_hash::ObjectId, _out: &mut Vec<u8>) -> Option<gix_object::Kind> {
97        None
98    }
99}
100
101impl<T: cache::Object + ?Sized> cache::Object for Box<T> {
102    fn put(&mut self, id: gix_hash::ObjectId, kind: gix_object::Kind, data: &[u8]) {
103        use std::ops::DerefMut;
104        self.deref_mut().put(id, kind, data);
105    }
106
107    fn get(&mut self, id: &gix_hash::ObjectId, out: &mut Vec<u8>) -> Option<gix_object::Kind> {
108        use std::ops::DerefMut;
109        self.deref_mut().get(id, out)
110    }
111}