1use std::ops::Range;
25use std::sync::Arc;
26
27use bytes::{Bytes, BytesMut};
28use indexmap::IndexMap;
29use parking_lot::Mutex;
30
31use crate::error::Result;
32use crate::source::ByteSource;
33
34const MAX_RESERVE: usize = 8 << 20;
40
41#[derive(Debug, Default)]
43struct Slot {
44 data: Mutex<Option<Bytes>>,
45}
46
47#[derive(Debug)]
48pub struct CachedSource<S: ByteSource> {
49 inner: S,
50 block_size: u64,
51 blocks: Mutex<IndexMap<u64, (u64, Arc<Slot>)>>,
53 tick: std::sync::atomic::AtomicU64,
56 max_blocks: usize,
57}
58
59impl<S: ByteSource> CachedSource<S> {
60 pub fn new(inner: S, block_size: u64, max_blocks: usize) -> Self {
61 assert!(block_size > 0, "block_size must be positive");
62 assert!(max_blocks > 0, "max_blocks must be positive");
63 Self {
64 inner,
65 block_size,
66 blocks: Mutex::new(IndexMap::with_capacity(max_blocks.min(1024))),
67 tick: std::sync::atomic::AtomicU64::new(0),
68 max_blocks,
69 }
70 }
71
72 pub fn into_inner(self) -> S {
73 self.inner
74 }
75
76 pub fn block_size(&self) -> u64 {
77 self.block_size
78 }
79
80 pub fn cached_blocks(&self) -> usize {
83 self.blocks.lock().len()
84 }
85
86 fn block_range(&self, offset: u64, len: usize) -> Range<u64> {
92 let first = offset / self.block_size;
93 let last = offset.saturating_add(len as u64).div_ceil(self.block_size);
94 first..last.max(first + 1)
95 }
96
97 fn clamped(&self, offset: u64, len: usize) -> usize {
113 match self.inner.len() {
114 Ok(total) => {
115 let left = total.saturating_sub(offset);
116 len.min(usize::try_from(left).unwrap_or(usize::MAX))
117 }
118 Err(_) => len,
119 }
120 }
121
122 fn slot(&self, block: u64) -> Arc<Slot> {
132 let mut map = self.blocks.lock();
133 let tick = self.tick.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
134 if let Some(entry) = map.get_mut(&block) {
135 entry.0 = tick;
136 return entry.1.clone();
137 }
138 let slot = Arc::new(Slot::default());
139 map.insert(block, (tick, slot.clone()));
140 while map.len() > self.max_blocks {
141 let oldest = map
142 .iter()
143 .enumerate()
144 .min_by_key(|(_, (_, (tick, _)))| *tick)
145 .map(|(index, _)| index)
146 .expect("the map is over its limit, so it is not empty");
147 map.swap_remove_index(oldest);
151 }
152 slot
153 }
154
155 fn fill(&self, block: u64, slot: &Slot) -> Result<Bytes> {
161 let mut guard = slot.data.lock();
162 if let Some(data) = guard.as_ref() {
163 return Ok(data.clone());
164 }
165 let data = self
166 .inner
167 .read_at(block * self.block_size, self.block_size as usize)?;
168 *guard = Some(data.clone());
169 Ok(data)
170 }
171
172 fn block(&self, block: u64) -> Result<Bytes> {
173 let slot = self.slot(block);
174 self.fill(block, &slot)
175 }
176}
177
178impl<S: ByteSource> ByteSource for CachedSource<S> {
179 fn path(&self) -> &str {
180 self.inner.path()
181 }
182
183 fn len(&self) -> Result<u64> {
184 self.inner.len()
185 }
186
187 fn read_at(&self, offset: u64, len: usize) -> Result<Bytes> {
188 let len = self.clamped(offset, len);
189 if len == 0 {
190 return Ok(Bytes::new());
191 }
192 let range = self.block_range(offset, len);
193
194 if range.end - range.start == 1 {
197 let data = self.block(range.start)?;
198 let start = (offset - range.start * self.block_size) as usize;
199 if start >= data.len() {
200 return Ok(Bytes::new());
201 }
202 let end = (start + len).min(data.len());
203 return Ok(data.slice(start..end));
204 }
205
206 let mut out = BytesMut::with_capacity(len.min(MAX_RESERVE));
212 let mut wanted = len;
213 let mut position = offset;
214 for index in range {
215 if wanted == 0 {
216 break;
217 }
218 let data = self.block(index)?;
219 let start = (position - index * self.block_size) as usize;
220 if start >= data.len() {
221 break; }
223 let take = wanted.min(data.len() - start);
224 out.extend_from_slice(&data[start..start + take]);
225 position += take as u64;
226 wanted -= take;
227 if data.len() < self.block_size as usize {
228 break; }
230 }
231 Ok(out.freeze())
232 }
233
234 fn prefetch(&self, ranges: &[Range<u64>]) {
235 self.inner.prefetch(ranges)
236 }
237
238 fn close(&self) {
239 self.blocks.lock().clear();
240 self.inner.close();
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::error::Error;
248 use std::sync::atomic::{AtomicUsize, Ordering};
249
250 #[derive(Debug)]
253 struct CountingSource {
254 data: Bytes,
255 reads: AtomicUsize,
256 fail_until: AtomicUsize,
257 }
258
259 impl CountingSource {
260 fn new(data: &[u8]) -> Self {
261 Self {
262 data: Bytes::copy_from_slice(data),
263 reads: AtomicUsize::new(0),
264 fail_until: AtomicUsize::new(0),
265 }
266 }
267 }
268
269 impl ByteSource for CountingSource {
270 fn path(&self) -> &str {
271 "memory"
272 }
273 fn len(&self) -> Result<u64> {
274 Ok(self.data.len() as u64)
275 }
276 fn read_at(&self, offset: u64, len: usize) -> Result<Bytes> {
277 self.reads.fetch_add(1, Ordering::SeqCst);
278 if self.fail_until.load(Ordering::SeqCst) > 0 {
279 self.fail_until.fetch_sub(1, Ordering::SeqCst);
280 return Err(Error::invalid("transient"));
281 }
282 let start = (offset as usize).min(self.data.len());
283 let end = (start + len).min(self.data.len());
284 Ok(self.data.slice(start..end))
285 }
286 }
287
288 fn data(n: usize) -> Vec<u8> {
289 (0..n).map(|i| (i % 251) as u8).collect()
290 }
291
292 #[test]
293 fn cached_reads_match_uncached_ones_for_every_range() {
294 let raw = data(1000);
295 for block_size in [1u64, 7, 64, 512, 4096] {
296 let cache = CachedSource::new(CountingSource::new(&raw), block_size, 64);
297 let plain = CountingSource::new(&raw);
298 for offset in [0u64, 1, 63, 64, 65, 511, 999, 1000, 1500] {
299 for len in [0usize, 1, 5, 64, 200, 1000] {
300 let a = cache.read_at(offset, len).unwrap();
301 let b = plain.read_at(offset, len).unwrap();
302 assert_eq!(a, b, "block_size={block_size} offset={offset} len={len}");
303 }
304 }
305 }
306 }
307
308 #[test]
314 fn a_length_larger_than_the_file_is_not_allocated() {
315 let raw = data(100);
316 for block_size in [4u64, 32, 4096] {
317 let cache = CachedSource::new(CountingSource::new(&raw), block_size, 4);
318 assert_eq!(cache.read_at(0, 1 << 60).unwrap(), &raw[..]);
319 assert_eq!(cache.read_at(50, usize::MAX).unwrap(), &raw[50..]);
320 assert!(cache.read_at(100, 1 << 60).unwrap().is_empty());
321 assert!(cache.read_at(u64::MAX - 1, 1 << 60).unwrap().is_empty());
323 }
324 }
325
326 #[test]
327 fn a_repeated_read_does_not_reach_the_source_again() {
328 let raw = data(1000);
329 let cache = CachedSource::new(CountingSource::new(&raw), 128, 64);
330 cache.read_at(0, 100).unwrap();
331 let after_first = cache.inner.reads.load(Ordering::SeqCst);
332 assert_eq!(after_first, 1);
333 for _ in 0..10 {
334 cache.read_at(0, 100).unwrap();
335 cache.read_at(20, 50).unwrap();
336 }
337 assert_eq!(cache.inner.reads.load(Ordering::SeqCst), after_first);
338 }
339
340 #[test]
341 fn the_lru_evicts_the_oldest_and_a_hit_renews_it() {
342 let raw = data(10_000);
343 let cache = CachedSource::new(CountingSource::new(&raw), 100, 3);
344 for block in 0..3u64 {
345 cache.read_at(block * 100, 10).unwrap();
346 }
347 assert_eq!(cache.cached_blocks(), 3);
348 cache.read_at(0, 10).unwrap();
350 cache.read_at(300, 10).unwrap();
351 assert_eq!(cache.cached_blocks(), 3);
352 let before = cache.inner.reads.load(Ordering::SeqCst);
353 cache.read_at(0, 10).unwrap(); assert_eq!(cache.inner.reads.load(Ordering::SeqCst), before);
355 cache.read_at(100, 10).unwrap(); assert_eq!(cache.inner.reads.load(Ordering::SeqCst), before + 1);
357 }
358
359 #[test]
360 fn a_failed_fill_is_retried_rather_than_cached() {
361 let raw = data(500);
362 let cache = CachedSource::new(CountingSource::new(&raw), 128, 8);
363 cache.inner.fail_until.store(1, Ordering::SeqCst);
364 assert!(cache.read_at(0, 10).is_err());
365 assert_eq!(&cache.read_at(0, 10).unwrap()[..], &raw[0..10]);
367 }
368
369 #[test]
370 fn concurrent_readers_of_one_block_fetch_it_once() {
371 let raw = data(1 << 16);
372 let cache = Arc::new(CachedSource::new(CountingSource::new(&raw), 1 << 12, 64));
373 let threads: Vec<_> = (0..16)
374 .map(|_| {
375 let cache = cache.clone();
376 let raw = raw.clone();
377 std::thread::spawn(move || {
378 for _ in 0..200 {
379 let got = cache.read_at(4096, 4096).unwrap();
380 assert_eq!(&got[..], &raw[4096..8192]);
381 }
382 })
383 })
384 .collect();
385 for t in threads {
386 t.join().unwrap();
387 }
388 assert_eq!(cache.inner.reads.load(Ordering::SeqCst), 1);
390 }
391
392 #[test]
393 fn close_drops_the_blocks_and_the_handle() {
394 let cache = CachedSource::new(CountingSource::new(&data(500)), 128, 8);
395 cache.read_at(0, 10).unwrap();
396 assert_eq!(cache.cached_blocks(), 1);
397 cache.close();
398 assert_eq!(cache.cached_blocks(), 0);
399 }
400}