ferripfs_blockstore/
caching.rs1use crate::{Block, Blockstore, BlockstoreResult};
15use cid::Cid;
16use lru::LruCache;
17use parking_lot::Mutex;
18use std::num::NonZeroUsize;
19
20pub const DEFAULT_CACHE_SIZE: usize = 256;
22
23pub struct CachingBlockstore<B: Blockstore> {
25 inner: B,
26 cache: Mutex<LruCache<String, Block>>,
27 has_cache: Mutex<LruCache<String, bool>>,
28}
29
30impl<B: Blockstore> CachingBlockstore<B> {
31 pub fn new(inner: B, cache_size: usize) -> Self {
33 let cache_size = NonZeroUsize::new(cache_size.max(1)).unwrap();
34 Self {
35 inner,
36 cache: Mutex::new(LruCache::new(cache_size)),
37 has_cache: Mutex::new(LruCache::new(cache_size)),
38 }
39 }
40
41 pub fn with_default_cache(inner: B) -> Self {
43 Self::new(inner, DEFAULT_CACHE_SIZE)
44 }
45
46 pub fn inner(&self) -> &B {
48 &self.inner
49 }
50
51 pub fn inner_mut(&mut self) -> &mut B {
53 &mut self.inner
54 }
55
56 pub fn cache_len(&self) -> usize {
58 self.cache.lock().len()
59 }
60
61 pub fn clear_cache(&self) {
63 self.cache.lock().clear();
64 self.has_cache.lock().clear();
65 }
66
67 pub fn cache_stats(&self) -> CacheStats {
69 let cache = self.cache.lock();
70 let has_cache = self.has_cache.lock();
71 CacheStats {
72 block_cache_len: cache.len(),
73 block_cache_cap: cache.cap().get(),
74 has_cache_len: has_cache.len(),
75 has_cache_cap: has_cache.cap().get(),
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct CacheStats {
83 pub block_cache_len: usize,
84 pub block_cache_cap: usize,
85 pub has_cache_len: usize,
86 pub has_cache_cap: usize,
87}
88
89impl<B: Blockstore> Blockstore for CachingBlockstore<B> {
90 fn get(&self, cid: &Cid) -> BlockstoreResult<Option<Block>> {
91 let key = cid.to_string();
92
93 {
95 let mut cache = self.cache.lock();
96 if let Some(block) = cache.get(&key) {
97 return Ok(Some(block.clone()));
98 }
99 }
100
101 let result = self.inner.get(cid)?;
103
104 if let Some(ref block) = result {
106 let mut cache = self.cache.lock();
107 cache.put(key.clone(), block.clone());
108 let mut has_cache = self.has_cache.lock();
109 has_cache.put(key, true);
110 }
111
112 Ok(result)
113 }
114
115 fn put(&mut self, block: Block) -> BlockstoreResult<()> {
116 let key = block.cid().to_string();
117
118 self.inner.put(block.clone())?;
120
121 {
123 let mut cache = self.cache.lock();
124 cache.put(key.clone(), block);
125 }
126 {
127 let mut has_cache = self.has_cache.lock();
128 has_cache.put(key, true);
129 }
130
131 Ok(())
132 }
133
134 fn has(&self, cid: &Cid) -> BlockstoreResult<bool> {
135 let key = cid.to_string();
136
137 {
139 let mut has_cache = self.has_cache.lock();
140 if let Some(&exists) = has_cache.get(&key) {
141 return Ok(exists);
142 }
143 }
144
145 {
147 let cache = self.cache.lock();
148 if cache.contains(&key) {
149 let mut has_cache = self.has_cache.lock();
150 has_cache.put(key, true);
151 return Ok(true);
152 }
153 }
154
155 let exists = self.inner.has(cid)?;
157
158 {
160 let mut has_cache = self.has_cache.lock();
161 has_cache.put(key, exists);
162 }
163
164 Ok(exists)
165 }
166
167 fn delete(&mut self, cid: &Cid) -> BlockstoreResult<()> {
168 let key = cid.to_string();
169
170 self.inner.delete(cid)?;
172
173 {
175 let mut cache = self.cache.lock();
176 cache.pop(&key);
177 }
178 {
179 let mut has_cache = self.has_cache.lock();
180 has_cache.pop(&key);
181 }
182
183 Ok(())
184 }
185
186 fn get_size(&self, cid: &Cid) -> BlockstoreResult<Option<usize>> {
187 let key = cid.to_string();
188
189 {
191 let mut cache = self.cache.lock();
192 if let Some(block) = cache.get(&key) {
193 return Ok(Some(block.size()));
194 }
195 }
196
197 self.inner.get_size(cid)
199 }
200
201 fn all_keys_chan(&self) -> BlockstoreResult<Box<dyn Iterator<Item = Cid> + '_>> {
202 self.inner.all_keys_chan()
204 }
205
206 fn hash_on_read(&self) -> bool {
207 self.inner.hash_on_read()
208 }
209
210 fn set_hash_on_read(&mut self, enabled: bool) {
211 self.inner.set_hash_on_read(enabled);
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::FlatFsBlockstore;
219 use tempfile::tempdir;
220
221 #[test]
222 fn test_caching_blockstore_get() {
223 let dir = tempdir().unwrap();
224 let inner = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
225 let mut bs = CachingBlockstore::new(inner, 10);
226
227 let block = Block::new_raw(b"cached data".to_vec()).unwrap();
229 let cid = *block.cid();
230 bs.put(block.clone()).unwrap();
231
232 let retrieved = bs.get(&cid).unwrap().unwrap();
234 assert_eq!(retrieved.data(), b"cached data");
235
236 assert_eq!(bs.cache_len(), 1);
238
239 let retrieved2 = bs.get(&cid).unwrap().unwrap();
241 assert_eq!(retrieved2.data(), b"cached data");
242 }
243
244 #[test]
245 fn test_caching_blockstore_has() {
246 let dir = tempdir().unwrap();
247 let inner = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
248 let mut bs = CachingBlockstore::new(inner, 10);
249
250 let block = Block::new_raw(b"test".to_vec()).unwrap();
251 let cid = *block.cid();
252
253 assert!(!bs.has(&cid).unwrap());
255
256 bs.put(block).unwrap();
258
259 assert!(bs.has(&cid).unwrap());
261 }
262
263 #[test]
264 fn test_caching_blockstore_delete() {
265 let dir = tempdir().unwrap();
266 let inner = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
267 let mut bs = CachingBlockstore::new(inner, 10);
268
269 let block = Block::new_raw(b"delete me".to_vec()).unwrap();
270 let cid = *block.cid();
271
272 bs.put(block).unwrap();
273 assert!(bs.has(&cid).unwrap());
274 assert_eq!(bs.cache_len(), 1);
275
276 bs.delete(&cid).unwrap();
277 assert!(!bs.has(&cid).unwrap());
278 assert_eq!(bs.cache_len(), 0);
279 }
280
281 #[test]
282 fn test_caching_blockstore_stats() {
283 let dir = tempdir().unwrap();
284 let inner = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
285 let mut bs = CachingBlockstore::new(inner, 100);
286
287 for i in 0..10 {
288 let block = Block::new_raw(format!("block {}", i).into_bytes()).unwrap();
289 bs.put(block).unwrap();
290 }
291
292 let stats = bs.cache_stats();
293 assert_eq!(stats.block_cache_len, 10);
294 assert_eq!(stats.block_cache_cap, 100);
295 }
296
297 #[test]
298 fn test_caching_blockstore_clear() {
299 let dir = tempdir().unwrap();
300 let inner = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
301 let mut bs = CachingBlockstore::new(inner, 10);
302
303 let block = Block::new_raw(b"clear test".to_vec()).unwrap();
304 let cid = *block.cid();
305 bs.put(block).unwrap();
306
307 assert_eq!(bs.cache_len(), 1);
308
309 bs.clear_cache();
310 assert_eq!(bs.cache_len(), 0);
311
312 assert!(bs.has(&cid).unwrap());
314 }
315}