fs_core/
caching_device.rs1use crate::block::{BlockDevice, BlockRead};
6use crate::error::Result;
7use std::collections::VecDeque;
8use std::sync::{Arc, Mutex};
9
10pub struct CachingDevice {
27 inner: Arc<dyn BlockRead>,
28 writable: Option<Arc<dyn BlockDevice>>,
33 block_size: u64,
34 state: Mutex<CacheState>,
35}
36
37struct CacheState {
38 entries: VecDeque<(u64, Arc<Vec<u8>>)>,
40 capacity: usize,
41 hits: u64,
42 misses: u64,
43}
44
45impl CachingDevice {
46 pub fn new(inner: Arc<dyn BlockDevice>, block_size: u64, capacity: usize) -> Arc<Self> {
49 Arc::new(Self {
50 inner: inner.clone(),
51 writable: Some(inner),
52 block_size,
53 state: Mutex::new(CacheState {
54 entries: VecDeque::with_capacity(capacity),
55 capacity,
56 hits: 0,
57 misses: 0,
58 }),
59 })
60 }
61
62 pub fn read_only(inner: Arc<dyn BlockRead>, block_size: u64, capacity: usize) -> Arc<Self> {
67 Arc::new(Self {
68 inner,
69 writable: None,
70 block_size,
71 state: Mutex::new(CacheState {
72 entries: VecDeque::with_capacity(capacity),
73 capacity,
74 hits: 0,
75 misses: 0,
76 }),
77 })
78 }
79
80 pub fn stats(&self) -> (u64, u64) {
81 let s = self.state.lock().unwrap();
82 (s.hits, s.misses)
83 }
84
85 pub fn invalidate_all(&self) {
86 let mut s = self.state.lock().unwrap();
87 s.entries.clear();
88 }
89
90 fn invalidate_range(state: &mut CacheState, start: u64, end: u64, block_size: u64) {
91 state.entries.retain(|(off, _)| {
92 let block_end = off.saturating_add(block_size);
93 *off >= end || block_end <= start
94 });
95 }
96}
97
98impl BlockRead for CachingDevice {
99 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
100 let cacheable =
101 buf.len() as u64 == self.block_size && offset.is_multiple_of(self.block_size);
102 if !cacheable {
103 return self.inner.read_at(offset, buf);
104 }
105
106 {
107 let mut s = self.state.lock().unwrap();
108 if let Some(pos) = s.entries.iter().position(|(o, _)| *o == offset) {
109 let entry = s.entries.remove(pos).unwrap();
110 buf.copy_from_slice(&entry.1);
111 s.entries.push_front(entry);
112 s.hits += 1;
113 return Ok(());
114 }
115 s.misses += 1;
116 }
117
118 self.inner.read_at(offset, buf)?;
119 let data = Arc::new(buf.to_vec());
120 let mut s = self.state.lock().unwrap();
121 if s.entries.len() >= s.capacity {
122 s.entries.pop_back();
123 }
124 s.entries.push_front((offset, data));
125 Ok(())
126 }
127
128 fn size_bytes(&self) -> u64 {
129 self.inner.size_bytes()
130 }
131}
132
133impl BlockDevice for CachingDevice {
134 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
135 let end = offset.saturating_add(buf.len() as u64);
140 {
141 let mut s = self.state.lock().unwrap();
142 let bs = self.block_size;
143 Self::invalidate_range(&mut s, offset, end, bs);
144 }
145 let Some(writable) = self.writable.as_ref() else {
146 return Err(crate::error::Error::ReadOnly);
147 };
148 writable.write_at(offset, buf)
149 }
150
151 fn flush(&self) -> Result<()> {
152 match self.writable.as_ref() {
153 Some(writable) => writable.flush(),
154 None => Ok(()),
158 }
159 }
160
161 fn is_writable(&self) -> bool {
162 self.writable.as_ref().is_some_and(|w| w.is_writable())
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::test_device::Bytes;
170
171 const BS: u64 = 512;
172
173 fn backing() -> Arc<Bytes> {
174 Arc::new(Bytes::new((0..4096u32).map(|i| i as u8).collect()))
175 }
176
177 #[test]
185 fn a_read_only_device_can_be_cached() {
186 let inner = backing();
187 let cache = CachingDevice::read_only(inner, BS, 4);
188
189 let mut first = vec![0u8; BS as usize];
190 let mut again = vec![0u8; BS as usize];
191 cache.read_at(0, &mut first).expect("first read");
192 cache.read_at(0, &mut again).expect("second read");
193
194 assert_eq!(first, again, "the cache must serve what the device held");
195 assert_eq!(cache.stats(), (1, 1), "one hit after one miss");
196 }
197
198 #[test]
201 fn writing_through_a_read_only_cache_is_refused() {
202 let cache = CachingDevice::read_only(backing(), BS, 4);
203 assert!(!cache.is_writable());
204 assert!(matches!(
205 cache.write_at(0, &[1u8; 8]),
206 Err(crate::error::Error::ReadOnly)
207 ));
208 assert!(cache.flush().is_ok());
211 }
212}