xref: /aosp_15_r20/external/crosvm/disk/src/qcow/vec_cache.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2018 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::collections::hash_map::IterMut;
6 use std::collections::HashMap;
7 use std::io;
8 use std::ops::Index;
9 use std::ops::IndexMut;
10 use std::slice::SliceIndex;
11 
12 /// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
13 /// it can be checked if they need to be committed to disk.
14 pub trait Cacheable {
15     /// Used to check if the item needs to be written out or if it can be discarded.
dirty(&self) -> bool16     fn dirty(&self) -> bool;
17 }
18 
19 #[derive(Debug)]
20 /// Represents a vector that implements the `Cacheable` trait so it can be held in a cache.
21 pub struct VecCache<T: 'static + Copy + Default> {
22     vec: Box<[T]>,
23     dirty: bool,
24 }
25 
26 impl<T: 'static + Copy + Default> VecCache<T> {
27     /// Creates a `VecCache` that can hold `count` elements.
new(count: usize) -> VecCache<T>28     pub fn new(count: usize) -> VecCache<T> {
29         VecCache {
30             vec: vec![Default::default(); count].into_boxed_slice(),
31             dirty: true,
32         }
33     }
34 
35     /// Creates a `VecCache` from the passed in `vec`.
from_vec(vec: Vec<T>) -> VecCache<T>36     pub fn from_vec(vec: Vec<T>) -> VecCache<T> {
37         VecCache {
38             vec: vec.into_boxed_slice(),
39             dirty: false,
40         }
41     }
42 
get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> where I: SliceIndex<[T]>,43     pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
44     where
45         I: SliceIndex<[T]>,
46     {
47         self.vec.get(index)
48     }
49 
50     /// Gets a reference to the underlying vector.
get_values(&self) -> &[T]51     pub fn get_values(&self) -> &[T] {
52         &self.vec
53     }
54 
55     /// Mark this cache element as clean.
mark_clean(&mut self)56     pub fn mark_clean(&mut self) {
57         self.dirty = false;
58     }
59 
60     /// Returns the number of elements in the vector.
len(&self) -> usize61     pub fn len(&self) -> usize {
62         self.vec.len()
63     }
64 }
65 
66 impl<T: 'static + Copy + Default> Cacheable for VecCache<T> {
dirty(&self) -> bool67     fn dirty(&self) -> bool {
68         self.dirty
69     }
70 }
71 
72 impl<T: 'static + Copy + Default> Index<usize> for VecCache<T> {
73     type Output = T;
74 
index(&self, index: usize) -> &T75     fn index(&self, index: usize) -> &T {
76         self.vec.index(index)
77     }
78 }
79 
80 impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> {
index_mut(&mut self, index: usize) -> &mut T81     fn index_mut(&mut self, index: usize) -> &mut T {
82         self.dirty = true;
83         self.vec.index_mut(index)
84     }
85 }
86 
87 #[derive(Debug)]
88 pub struct CacheMap<T: Cacheable> {
89     capacity: usize,
90     map: HashMap<usize, T>,
91 }
92 
93 impl<T: Cacheable> CacheMap<T> {
new(capacity: usize) -> Self94     pub fn new(capacity: usize) -> Self {
95         CacheMap {
96             capacity,
97             map: HashMap::with_capacity(capacity),
98         }
99     }
100 
contains_key(&self, key: &usize) -> bool101     pub fn contains_key(&self, key: &usize) -> bool {
102         self.map.contains_key(key)
103     }
104 
get(&self, index: &usize) -> Option<&T>105     pub fn get(&self, index: &usize) -> Option<&T> {
106         self.map.get(index)
107     }
108 
get_mut(&mut self, index: &usize) -> Option<&mut T>109     pub fn get_mut(&mut self, index: &usize) -> Option<&mut T> {
110         self.map.get_mut(index)
111     }
112 
iter_mut(&mut self) -> IterMut<usize, T>113     pub fn iter_mut(&mut self) -> IterMut<usize, T> {
114         self.map.iter_mut()
115     }
116 
117     // 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<()>,118     pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()>
119     where
120         F: FnOnce(usize, T) -> io::Result<()>,
121     {
122         if self.map.len() == self.capacity {
123             // TODO(dgreid) - smarter eviction strategy.
124             let to_evict = *self.map.iter().next().unwrap().0;
125             if let Some(evicted) = self.map.remove(&to_evict) {
126                 if evicted.dirty() {
127                     write_callback(to_evict, evicted)?;
128                 }
129             }
130         }
131         self.map.insert(index, block);
132         Ok(())
133     }
134 }
135 
136 #[cfg(test)]
137 mod tests {
138     use super::*;
139 
140     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
141     struct NumCache(pub u64);
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(5), |index, _| {
154                 evicted = Some(index);
155                 Ok(())
156             })
157             .unwrap();
158         assert_eq!(evicted, None);
159         cache
160             .insert(1, NumCache(6), |index, _| {
161                 evicted = Some(index);
162                 Ok(())
163             })
164             .unwrap();
165         assert_eq!(evicted, None);
166         cache
167             .insert(2, NumCache(7), |index, _| {
168                 evicted = Some(index);
169                 Ok(())
170             })
171             .unwrap();
172         assert_eq!(evicted, None);
173         cache
174             .insert(3, NumCache(8), |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         assert_eq!(cache.get(&3), Some(&NumCache(8)));
187     }
188 }
189