From 62d21839e6aa160f6db1957be018f4cebe36e397 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Tue, 28 Aug 2018 17:07:56 -0700 Subject: [PATCH] qcow: Add vec_cache The `VecCache` struct will be used to represent the file clusters in caches. It ties a vector to a state of dirty or clean. Change-Id: I474eb67d2ad9f086da638ecc385ccce74737d3b9 Signed-off-by: Dylan Reid Reviewed-on: https://chromium-review.googlesource.com/1207451 Reviewed-by: Chirantan Ekbote --- qcow/src/vec_cache.rs | 172 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 qcow/src/vec_cache.rs diff --git a/qcow/src/vec_cache.rs b/qcow/src/vec_cache.rs new file mode 100644 index 0000000000..4144eb4f6e --- /dev/null +++ b/qcow/src/vec_cache.rs @@ -0,0 +1,172 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::collections::hash_map::IterMut; +use std::collections::HashMap; +use std::io; +use std::ops::{Index, IndexMut}; + +/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so +/// it can be checked if they need to be committed to disk. +pub trait Cacheable { + /// Used to check if the item needs to be written out or if it can be discarded. + fn dirty(&self) -> bool; +} + +#[derive(Debug)] +/// Represents a vector that implements the `Cacheable` trait so it can be held in a cache. +pub struct VecCache { + vec: Box<[T]>, + dirty: bool, +} + +impl VecCache { + /// Creates a `VecCache` that can hold `count` elements. + pub fn new(count: usize) -> VecCache { + VecCache { + vec: vec![Default::default(); count].into_boxed_slice(), + dirty: true, + } + } + + /// Creates a `VecCache` from the passed in `vec`. + pub fn from_vec(vec: Vec) -> VecCache { + VecCache { + vec: vec.into_boxed_slice(), + dirty: false, + } + } + + /// Gets a reference to the underlying vector. + pub fn get_values(&self) -> &[T] { + &self.vec + } + + /// Mark this cache element as clean. + pub fn mark_clean(&mut self) { + self.dirty = false; + } +} + +impl Cacheable for VecCache { + fn dirty(&self) -> bool { + self.dirty + } +} + +impl Index for VecCache { + type Output = T; + + fn index(&self, index: usize) -> &T { + self.vec.index(index) + } +} + +impl IndexMut for VecCache { + fn index_mut(&mut self, index: usize) -> &mut T { + self.dirty = true; + self.vec.index_mut(index) + } +} + +#[derive(Debug)] +pub struct CacheMap { + capacity: usize, + map: HashMap, +} + +impl CacheMap { + pub fn new(capacity: usize) -> Self { + CacheMap { + capacity, + map: HashMap::with_capacity(capacity), + } + } + + pub fn contains_key(&self, key: &usize) -> bool { + self.map.contains_key(key) + } + + pub fn get(&self, index: &usize) -> Option<&T> { + self.map.get(index) + } + + pub fn get_mut(&mut self, index: &usize) -> Option<&mut T> { + self.map.get_mut(index) + } + + pub fn iter_mut(&mut self) -> IterMut { + self.map.iter_mut() + } + + // Check if the refblock cache is full and we need to evict. + pub fn insert(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> + where + F: FnOnce(usize, T) -> io::Result<()>, + { + if self.map.len() == self.capacity { + // TODO(dgreid) - smarter eviction strategy. + let to_evict = *self.map.iter().nth(0).unwrap().0; + if let Some(evicted) = self.map.remove(&to_evict) { + if evicted.dirty() { + write_callback(to_evict, evicted)?; + } + } + } + self.map.insert(index, block); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct NumCache(pub u64); + impl Cacheable for NumCache { + fn dirty(&self) -> bool { + true + } + } + + #[test] + fn evicts_when_full() { + let mut cache = CacheMap::::new(3); + let mut evicted = None; + cache + .insert(0, NumCache(5), |index, _| { + evicted = Some(index); + Ok(()) + }) + .unwrap(); + assert_eq!(evicted, None); + cache + .insert(1, NumCache(6), |index, _| { + evicted = Some(index); + Ok(()) + }) + .unwrap(); + assert_eq!(evicted, None); + cache + .insert(2, NumCache(7), |index, _| { + evicted = Some(index); + Ok(()) + }) + .unwrap(); + assert_eq!(evicted, None); + cache + .insert(3, NumCache(8), |index, _| { + evicted = Some(index); + Ok(()) + }) + .unwrap(); + assert!(evicted.is_some()); + + // Check that three of the four items inserted are still there and that the most recently + // inserted is one of them. + let num_items = (0..=3).filter(|k| cache.contains_key(&k)).count(); + assert_eq!(num_items, 3); + assert!(cache.contains_key(&3)); + } +}