use std::{
collections::HashMap,
hash::Hash,
sync::{Arc, Weak},
};
use datasize::DataSize;
#[derive(DataSize, Debug)]
pub(super) struct BlobCache<I> {
#[data_size(skip)]
items: HashMap<I, Weak<Vec<u8>>>,
garbage_collect_interval: u16,
put_count: u16,
}
impl<I> BlobCache<I> {
pub(super) fn new(garbage_collect_interval: u16) -> Self {
Self {
items: HashMap::new(),
garbage_collect_interval,
put_count: 0,
}
}
}
impl<I> BlobCache<I>
where
I: Hash + Eq,
{
pub(super) fn put(&mut self, id: I, item: Weak<Vec<u8>>) {
self.items.insert(id, item);
if self.put_count >= self.garbage_collect_interval {
self.items.retain(|_, item| item.strong_count() > 0);
self.put_count = 0;
}
self.put_count += 1;
}
pub(super) fn get(&self, id: &I) -> Option<Arc<Vec<u8>>> {
self.items.get(id).and_then(Weak::upgrade)
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use datasize::DataSize;
use super::BlobCache;
use crate::types::{Deploy, Item};
impl<I> BlobCache<I>
where
I: DataSize,
{
fn num_entries(&self) -> usize {
self.items.len()
}
}
#[test]
fn can_load_and_store_items() {
let mut cache: BlobCache<<Deploy as Item>::Id> = BlobCache::new(5);
let mut rng = crate::new_rng();
let d1 = Deploy::random(&mut rng);
let d2 = Deploy::random(&mut rng);
let d1_id = *d1.id();
let d2_id = *d2.id();
let d1_serialized = bincode::serialize(&d1).expect("could not serialize first deploy");
let d2_serialized = bincode::serialize(&d2).expect("could not serialize second deploy");
let d1_shared = Arc::new(d1_serialized);
let d2_shared = Arc::new(d2_serialized);
assert!(cache.get(&d1_id).is_none());
assert!(cache.get(&d2_id).is_none());
cache.put(d1_id, Arc::downgrade(&d1_shared));
assert!(Arc::ptr_eq(
&cache.get(&d1_id).expect("did not find d1"),
&d1_shared
));
assert!(cache.get(&d2_id).is_none());
cache.put(d2_id, Arc::downgrade(&d2_shared));
assert!(Arc::ptr_eq(
&cache.get(&d1_id).expect("did not find d1"),
&d1_shared
));
assert!(Arc::ptr_eq(
&cache.get(&d2_id).expect("did not find d1"),
&d2_shared
));
}
#[test]
fn frees_memory_after_reference_loss() {
let mut cache: BlobCache<<Deploy as Item>::Id> = BlobCache::new(5);
let mut rng = crate::new_rng();
let d1 = Deploy::random(&mut rng);
let d1_id = *d1.id();
let d1_serialized = bincode::serialize(&d1).expect("could not serialize first deploy");
let d1_shared = Arc::new(d1_serialized);
assert!(cache.get(&d1_id).is_none());
cache.put(d1_id, Arc::downgrade(&d1_shared));
assert!(Arc::ptr_eq(
&cache.get(&d1_id).expect("did not find d1"),
&d1_shared
));
drop(d1_shared);
assert!(cache.get(&d1_id).is_none());
}
#[test]
fn garbage_is_collected() {
let mut cache: BlobCache<<Deploy as Item>::Id> = BlobCache::new(5);
let mut rng = crate::new_rng();
assert_eq!(cache.num_entries(), 0);
for i in 0..5 {
let deploy = Deploy::random(&mut rng);
let id = *deploy.id();
let serialized = bincode::serialize(&deploy).expect("could not serialize first deploy");
let shared = Arc::new(serialized);
cache.put(id, Arc::downgrade(&shared));
assert_eq!(cache.num_entries(), i + 1);
drop(shared);
assert_eq!(cache.num_entries(), i + 1);
}
let deploy = Deploy::random(&mut rng);
let id = *deploy.id();
let serialized = bincode::serialize(&deploy).expect("could not serialize first deploy");
let shared = Arc::new(serialized);
cache.put(id, Arc::downgrade(&shared));
assert_eq!(cache.num_entries(), 1);
drop(shared);
assert_eq!(cache.num_entries(), 1);
}
}