1 // Copyright 2018 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE-BSD-3-Clause file. 4 // 5 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 6 7 use std::collections::hash_map::IterMut; 8 use std::collections::HashMap; 9 use std::io; 10 use std::ops::{Index, IndexMut}; 11 use std::slice::SliceIndex; 12 13 /// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so 14 /// it can be checked if they need to be committed to disk. 15 pub trait Cacheable { 16 /// Used to check if the item needs to be written out or if it can be discarded. dirty(&self) -> bool17 fn dirty(&self) -> bool; 18 } 19 20 #[derive(Clone, Debug)] 21 /// Represents a vector that implements the `Cacheable` trait so it can be held in a cache. 22 pub struct VecCache<T: 'static + Copy + Default> { 23 vec: Box<[T]>, 24 dirty: bool, 25 } 26 27 impl<T: 'static + Copy + Default> VecCache<T> { 28 /// Creates a `VecCache` that can hold `count` elements. new(count: usize) -> VecCache<T>29 pub fn new(count: usize) -> VecCache<T> { 30 VecCache { 31 vec: vec![Default::default(); count].into_boxed_slice(), 32 dirty: true, 33 } 34 } 35 36 /// Creates a `VecCache` from the passed in `vec`. from_vec(vec: Vec<T>) -> VecCache<T>37 pub fn from_vec(vec: Vec<T>) -> VecCache<T> { 38 VecCache { 39 vec: vec.into_boxed_slice(), 40 dirty: false, 41 } 42 } 43 get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> where I: SliceIndex<[T]>,44 pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> 45 where 46 I: SliceIndex<[T]>, 47 { 48 self.vec.get(index) 49 } 50 51 /// Gets a reference to the underlying vector. get_values(&self) -> &[T]52 pub fn get_values(&self) -> &[T] { 53 &self.vec 54 } 55 56 /// Mark this cache element as clean. mark_clean(&mut self)57 pub fn mark_clean(&mut self) { 58 self.dirty = false; 59 } 60 61 /// Returns the number of elements in the vector. len(&self) -> usize62 pub fn len(&self) -> usize { 63 self.vec.len() 64 } 65 } 66 67 impl<T: 'static + Copy + Default> Cacheable for VecCache<T> { dirty(&self) -> bool68 fn dirty(&self) -> bool { 69 self.dirty 70 } 71 } 72 73 impl<T: 'static + Copy + Default> Index<usize> for VecCache<T> { 74 type Output = T; 75 index(&self, index: usize) -> &T76 fn index(&self, index: usize) -> &T { 77 self.vec.index(index) 78 } 79 } 80 81 impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> { index_mut(&mut self, index: usize) -> &mut T82 fn index_mut(&mut self, index: usize) -> &mut T { 83 self.dirty = true; 84 self.vec.index_mut(index) 85 } 86 } 87 88 #[derive(Clone, Debug)] 89 pub struct CacheMap<T: Cacheable> { 90 capacity: usize, 91 map: HashMap<usize, T>, 92 } 93 94 impl<T: Cacheable> CacheMap<T> { new(capacity: usize) -> Self95 pub fn new(capacity: usize) -> Self { 96 CacheMap { 97 capacity, 98 map: HashMap::with_capacity(capacity), 99 } 100 } 101 contains_key(&self, key: usize) -> bool102 pub fn contains_key(&self, key: usize) -> bool { 103 self.map.contains_key(&key) 104 } 105 get(&self, index: usize) -> Option<&T>106 pub fn get(&self, index: usize) -> Option<&T> { 107 self.map.get(&index) 108 } 109 get_mut(&mut self, index: usize) -> Option<&mut T>110 pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { 111 self.map.get_mut(&index) 112 } 113 iter_mut(&mut self) -> IterMut<'_, usize, T>114 pub fn iter_mut(&mut self) -> IterMut<'_, usize, T> { 115 self.map.iter_mut() 116 } 117 118 // Check if the refblock cache is full and we need to evict. insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> where F: FnOnce(usize, T) -> io::Result<()>,119 pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> 120 where 121 F: FnOnce(usize, T) -> io::Result<()>, 122 { 123 if self.map.len() == self.capacity { 124 // TODO(dgreid) - smarter eviction strategy. 125 let to_evict = *self.map.iter().next().unwrap().0; 126 if let Some(evicted) = self.map.remove(&to_evict) { 127 if evicted.dirty() { 128 write_callback(to_evict, evicted)?; 129 } 130 } 131 } 132 self.map.insert(index, block); 133 Ok(()) 134 } 135 } 136 137 #[cfg(test)] 138 mod tests { 139 use super::*; 140 141 struct NumCache(()); 142 impl Cacheable for NumCache { dirty(&self) -> bool143 fn dirty(&self) -> bool { 144 true 145 } 146 } 147 148 #[test] evicts_when_full()149 fn evicts_when_full() { 150 let mut cache = CacheMap::<NumCache>::new(3); 151 let mut evicted = None; 152 cache 153 .insert(0, NumCache(()), |index, _| { 154 evicted = Some(index); 155 Ok(()) 156 }) 157 .unwrap(); 158 assert_eq!(evicted, None); 159 cache 160 .insert(1, NumCache(()), |index, _| { 161 evicted = Some(index); 162 Ok(()) 163 }) 164 .unwrap(); 165 assert_eq!(evicted, None); 166 cache 167 .insert(2, NumCache(()), |index, _| { 168 evicted = Some(index); 169 Ok(()) 170 }) 171 .unwrap(); 172 assert_eq!(evicted, None); 173 cache 174 .insert(3, NumCache(()), |index, _| { 175 evicted = Some(index); 176 Ok(()) 177 }) 178 .unwrap(); 179 assert!(evicted.is_some()); 180 181 // Check that three of the four items inserted are still there and that the most recently 182 // inserted is one of them. 183 let num_items = (0..=3).filter(|k| cache.contains_key(*k)).count(); 184 assert_eq!(num_items, 3); 185 assert!(cache.contains_key(3)); 186 } 187 } 188