mithril_common/digesters/cache/
memory_provider.rs1use crate::{
2 digesters::cache::CacheProviderResult,
3 digesters::cache::ImmutableFileDigestCacheProvider,
4 digesters::ImmutableFile,
5 entities::{HexEncodedDigest, ImmutableFileName},
6};
7
8use async_trait::async_trait;
9use std::collections::{BTreeMap, HashMap};
10use tokio::sync::RwLock;
11
12pub struct MemoryImmutableFileDigestCacheProvider {
14 store: RwLock<HashMap<ImmutableFileName, HexEncodedDigest>>,
15}
16
17impl MemoryImmutableFileDigestCacheProvider {
18 pub fn from(values: HashMap<ImmutableFileName, HexEncodedDigest>) -> Self {
20 Self {
21 store: RwLock::new(values),
22 }
23 }
24}
25
26impl Default for MemoryImmutableFileDigestCacheProvider {
27 fn default() -> Self {
28 Self {
29 store: RwLock::new(HashMap::new()),
30 }
31 }
32}
33
34#[async_trait]
35impl ImmutableFileDigestCacheProvider for MemoryImmutableFileDigestCacheProvider {
36 async fn store(
37 &self,
38 digest_per_filenames: Vec<(ImmutableFileName, HexEncodedDigest)>,
39 ) -> CacheProviderResult<()> {
40 let mut store = self.store.write().await;
41 for (filename, digest) in digest_per_filenames {
42 store.insert(filename, digest);
43 }
44
45 Ok(())
46 }
47
48 async fn get(
49 &self,
50 immutables: Vec<ImmutableFile>,
51 ) -> CacheProviderResult<BTreeMap<ImmutableFile, Option<HexEncodedDigest>>> {
52 let store = self.store.read().await;
53 let mut result = BTreeMap::new();
54
55 for immutable in immutables {
56 let value = store.get(&immutable.filename).map(|f| f.to_owned());
57 result.insert(immutable, value);
58 }
59
60 Ok(result)
61 }
62
63 async fn reset(&self) -> CacheProviderResult<()> {
64 let mut store = self.store.write().await;
65 store.clear();
66 Ok(())
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::{
73 digesters::cache::{
74 ImmutableFileDigestCacheProvider, MemoryImmutableFileDigestCacheProvider,
75 },
76 digesters::ImmutableFile,
77 };
78 use std::collections::{BTreeMap, HashMap};
79 use std::path::PathBuf;
80
81 #[tokio::test]
82 async fn can_store_values() {
83 let provider = MemoryImmutableFileDigestCacheProvider::default();
84 let values_to_store = vec![
85 ("0.chunk".to_string(), "digest 0".to_string()),
86 ("1.chunk".to_string(), "digest 1".to_string()),
87 ];
88 let expected: BTreeMap<_, _> = BTreeMap::from([
89 (
90 ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk"),
91 Some("digest 0".to_string()),
92 ),
93 (
94 ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk"),
95 Some("digest 1".to_string()),
96 ),
97 ]);
98 let immutables = expected.keys().cloned().collect();
99
100 provider
101 .store(values_to_store)
102 .await
103 .expect("Cache write should not fail");
104 let result = provider
105 .get(immutables)
106 .await
107 .expect("Cache read should not fail");
108
109 assert_eq!(expected, result);
110 }
111
112 #[tokio::test]
113 async fn returns_only_asked_immutables_cache() {
114 let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([
115 ("0.chunk".to_string(), "digest 0".to_string()),
116 ("1.chunk".to_string(), "digest 1".to_string()),
117 ]));
118 let expected: BTreeMap<_, _> = BTreeMap::from([(
119 ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk"),
120 Some("digest 0".to_string()),
121 )]);
122 let immutables = expected.keys().cloned().collect();
123
124 let result = provider
125 .get(immutables)
126 .await
127 .expect("Cache read should not fail");
128
129 assert_eq!(expected, result);
130 }
131
132 #[tokio::test]
133 async fn returns_none_for_uncached_asked_immutables() {
134 let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([(
135 "0.chunk".to_string(),
136 "digest 0".to_string(),
137 )]));
138 let expected: BTreeMap<_, _> =
139 BTreeMap::from([(ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk"), None)]);
140 let immutables = expected.keys().cloned().collect();
141
142 let result = provider
143 .get(immutables)
144 .await
145 .expect("Cache read should not fail");
146
147 assert_eq!(expected, result);
148 }
149
150 #[tokio::test]
151 async fn store_erase_existing_values() {
152 let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([
153 ("0.chunk".to_string(), "to erase".to_string()),
154 ("1.chunk".to_string(), "keep me".to_string()),
155 ("2.chunk".to_string(), "keep me too".to_string()),
156 ]));
157 let values_to_store = vec![
158 ("0.chunk".to_string(), "updated".to_string()),
159 ("1.chunk".to_string(), "keep me".to_string()),
160 ];
161 let expected: BTreeMap<_, _> = BTreeMap::from([
162 (
163 ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk"),
164 Some("updated".to_string()),
165 ),
166 (
167 ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk"),
168 Some("keep me".to_string()),
169 ),
170 (
171 ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk"),
172 Some("keep me too".to_string()),
173 ),
174 (ImmutableFile::dummy(PathBuf::default(), 3, "3.chunk"), None),
175 ]);
176 let immutables = expected.keys().cloned().collect();
177
178 provider
179 .store(values_to_store)
180 .await
181 .expect("Cache write should not fail");
182 let result = provider
183 .get(immutables)
184 .await
185 .expect("Cache read should not fail");
186
187 assert_eq!(expected, result);
188 }
189
190 #[tokio::test]
191 async fn reset_clear_existing_values() {
192 let provider = MemoryImmutableFileDigestCacheProvider::default();
193 let values_to_store = vec![
194 ("0.chunk".to_string(), "digest 0".to_string()),
195 ("1.chunk".to_string(), "digest 1".to_string()),
196 ];
197 let expected: BTreeMap<_, _> = BTreeMap::from([
198 (
199 ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk"),
200 Some("digest 0".to_string()),
201 ),
202 (
203 ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk"),
204 Some("digest 1".to_string()),
205 ),
206 ]);
207 let immutables = expected.keys().cloned().collect();
208
209 provider
210 .store(values_to_store)
211 .await
212 .expect("Cache write should not fail");
213 provider.reset().await.expect("reset should not fails");
214
215 let result: BTreeMap<_, _> = provider
216 .get(immutables)
217 .await
218 .expect("Cache read should not fail");
219
220 assert!(result.into_iter().all(|(_, cache)| cache.is_none()));
221 }
222}