//! # Note//!//! This module is a bit 'misplaced' if spelled out like 'git_pack::cache::object::*' but is best placed here for code re-use and//! general usefulnes.use std::{ convert::TryInto, hash::{BuildHasher, Hasher},}; use crate::cache; #[cfg(feature = "object-cache-dynamic")]mod memory { use std::num::NonZeroUsize; use clru::WeightScale; use crate::cache; struct Entry { data: Vec<u8>, kind: git_object::Kind, } type Key = git_hash::ObjectId; struct CustomScale; impl WeightScale<Key, Entry> for CustomScale { fn weight(&self, key: &Key, value: &Entry) -> usize { value.data.len() + std::mem::size_of::<Entry>() + key.as_bytes().len() } } /// An LRU cache with hash map backing and an eviction rule based on the memory usage for object data in bytes. pub struct MemoryCappedHashmap { inner: clru::CLruCache<Key, Entry, super::State, CustomScale>, free_list: Vec<Vec<u8>>, debug: git_features::cache::Debug, } impl MemoryCappedHashmap { /// The amount of bytes we can hold in total, or the value we saw in `new(…)`. pub fn capacity(&self) -> usize { self.inner.capacity() } /// Return a new instance which evicts least recently used items if it uses more than `memory_cap_in_bytes` /// object data. pub fn new(memory_cap_in_bytes: usize) -> MemoryCappedHashmap { MemoryCappedHashmap { inner: clru::CLruCache::with_config( clru::CLruCacheConfig::new(NonZeroUsize::new(memory_cap_in_bytes).expect("non zero")) .with_hasher(super::State::default()) .with_scale(CustomScale), ), free_list: Vec::new(), debug: git_features::cache::Debug::new(format!("MemoryCappedObjectHashmap({}B)", memory_cap_in_bytes)), } } } impl cache::Object for MemoryCappedHashmap { /// Put the object going by `id` of `kind` with `data` into the cache. fn put(&mut self, id: git_hash::ObjectId, kind: git_object::Kind, data: &[u8]) { self.debug.put(); if let Ok(Some(previous_entry)) = self.inner.put_with_weight( id, Entry { data: self .free_list .pop() .map(|mut v| { v.clear(); v.resize(data.len(), 0); v.copy_from_slice(data); v }) .unwrap_or_else(|| Vec::from(data)), kind, }, ) { self.free_list.push(previous_entry.data) } } /// Try to retrieve the object named `id` and place its data into `out` if available and return `Some(kind)` if found. fn get(&mut self, id: &git_hash::ObjectId, out: &mut Vec<u8>) -> Option<git_object::Kind> { let res = self.inner.get(id).map(|e| { out.resize(e.data.len(), 0); out.copy_from_slice(&e.data); e.kind }); if res.is_some() { self.debug.hit() } else { self.debug.miss() } res } }}#[cfg(feature = "object-cache-dynamic")]pub use memory::MemoryCappedHashmap; /// A state suitable for hash maps tuned to just reuse the key assuming it is an object id.#[derive(Default, Clone)]pub(crate) struct State; #[derive(Default, Clone)]pub(crate) struct OidHasher { digest: u64,} impl Hasher for OidHasher { fn finish(&self) -> u64 { self.digest } fn write(&mut self, bytes: &[u8]) { self.digest = u64::from_be_bytes(bytes[..8].try_into().expect("any git hash has more than 8 bytes")); }} impl BuildHasher for State { type Hasher = OidHasher; fn build_hasher(&self) -> Self::Hasher { OidHasher::default() }} /// A cache implementation that doesn't do any caching.pub struct Never; impl cache::Object for Never { /// Noop fn put(&mut self, _id: git_hash::ObjectId, _kind: git_object::Kind, _data: &[u8]) {} /// Noop fn get(&mut self, _id: &git_hash::ObjectId, _out: &mut Vec<u8>) -> Option<git_object::Kind> { None }} impl<T: cache::Object + ?Sized> cache::Object for Box<T> { fn put(&mut self, id: git_hash::ObjectId, kind: git_object::Kind, data: &[u8]) { use std::ops::DerefMut; self.deref_mut().put(id, kind, data) } fn get(&mut self, id: &git_hash::ObjectId, out: &mut Vec<u8>) -> Option<git_object::Kind> { use std::ops::DerefMut; self.deref_mut().get(id, out) }}
//! # Note
//!
//! This module is a bit 'misplaced' if spelled out like 'git_pack::cache::object::*' but is best placed here for code re-use and
//! general usefulnes.
use std::{
convert::TryInto,
hash::{BuildHasher, Hasher},
};
use crate::cache;
#[cfg(feature = "object-cache-dynamic")]
mod memory {
use std::num::NonZeroUsize;
use clru::WeightScale;
use crate::cache;
struct Entry {
data: Vec<u8>,
kind: git_object::Kind,
}
type Key = git_hash::ObjectId;
struct CustomScale;
impl WeightScale<Key, Entry> for CustomScale {
fn weight(&self, key: &Key, value: &Entry) -> usize {
value.data.len() + std::mem::size_of::<Entry>() + key.as_bytes().len()
}
}
/// An LRU cache with hash map backing and an eviction rule based on the memory usage for object data in bytes.
pub struct MemoryCappedHashmap {
inner: clru::CLruCache<Key, Entry, super::State, CustomScale>,
free_list: Vec<Vec<u8>>,
debug: git_features::cache::Debug,
}
impl MemoryCappedHashmap {
/// The amount of bytes we can hold in total, or the value we saw in `new(…)`.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
/// Return a new instance which evicts least recently used items if it uses more than `memory_cap_in_bytes`
/// object data.
pub fn new(memory_cap_in_bytes: usize) -> MemoryCappedHashmap {
MemoryCappedHashmap {
inner: clru::CLruCache::with_config(
clru::CLruCacheConfig::new(NonZeroUsize::new(memory_cap_in_bytes).expect("non zero"))
.with_hasher(super::State::default())
.with_scale(CustomScale),
),
free_list: Vec::new(),
debug: git_features::cache::Debug::new(format!("MemoryCappedObjectHashmap({}B)", memory_cap_in_bytes)),
}
}
}
impl cache::Object for MemoryCappedHashmap {
/// Put the object going by `id` of `kind` with `data` into the cache.
fn put(&mut self, id: git_hash::ObjectId, kind: git_object::Kind, data: &[u8]) {
self.debug.put();
if let Ok(Some(previous_entry)) = self.inner.put_with_weight(
id,
Entry {
data: self
.free_list
.pop()
.map(|mut v| {
v.clear();
v.resize(data.len(), 0);
v.copy_from_slice(data);
v
})
.unwrap_or_else(|| Vec::from(data)),
kind,
},
) {
self.free_list.push(previous_entry.data)
}
}
/// Try to retrieve the object named `id` and place its data into `out` if available and return `Some(kind)` if found.
fn get(&mut self, id: &git_hash::ObjectId, out: &mut Vec<u8>) -> Option<git_object::Kind> {
let res = self.inner.get(id).map(|e| {
out.resize(e.data.len(), 0);
out.copy_from_slice(&e.data);
e.kind
});
if res.is_some() {
self.debug.hit()
} else {
self.debug.miss()
}
res
}
}
}
#[cfg(feature = "object-cache-dynamic")]
pub use memory::MemoryCappedHashmap;
/// A state suitable for hash maps tuned to just reuse the key assuming it is an object id.
#[derive(Default, Clone)]
pub(crate) struct State;
#[derive(Default, Clone)]
pub(crate) struct OidHasher {
digest: u64,
}
impl Hasher for OidHasher {
fn finish(&self) -> u64 {
self.digest
}
fn write(&mut self, bytes: &[u8]) {
self.digest = u64::from_be_bytes(bytes[..8].try_into().expect("any git hash has more than 8 bytes"));
}
}
impl BuildHasher for State {
type Hasher = OidHasher;
fn build_hasher(&self) -> Self::Hasher {
OidHasher::default()
}
}
/// A cache implementation that doesn't do any caching.
pub struct Never;
impl cache::Object for Never {
/// Noop
fn put(&mut self, _id: git_hash::ObjectId, _kind: git_object::Kind, _data: &[u8]) {}
/// Noop
fn get(&mut self, _id: &git_hash::ObjectId, _out: &mut Vec<u8>) -> Option<git_object::Kind> {
None
}
}
impl<T: cache::Object + ?Sized> cache::Object for Box<T> {
fn put(&mut self, id: git_hash::ObjectId, kind: git_object::Kind, data: &[u8]) {
use std::ops::DerefMut;
self.deref_mut().put(id, kind, data)
}
fn get(&mut self, id: &git_hash::ObjectId, out: &mut Vec<u8>) -> Option<git_object::Kind> {
use std::ops::DerefMut;
self.deref_mut().get(id, out)
}
}