git_pack/cache/
object.rs

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