1use crate::reader::RangeReader;
28use std::collections::{BTreeSet, HashMap};
29use std::sync::{Arc, Mutex};
30
31pub const DEFAULT_BLOCK: u64 = 64 * 1024;
34
35pub const DEFAULT_CACHE_CAP: u64 = 256 * 1024 * 1024;
42
43pub fn auto_block(len: u64) -> u64 {
50 const MB: u64 = 1 << 20;
51 let mult: u64 = if len > 100 * MB {
59 2 } else {
61 1 };
63 mult * DEFAULT_BLOCK
64}
65
66struct CacheEntry {
68 data: Arc<[u8]>,
69 stamp: u64,
70}
71
72struct CacheState {
75 map: HashMap<u64, CacheEntry>,
76 used: u64,
77 tick: u64,
78}
79
80pub struct BlockCacheReader<R> {
81 inner: R,
82 block: u64,
83 len: u64,
84 cap: u64,
85 cache: Mutex<CacheState>,
86}
87
88impl<R: RangeReader> BlockCacheReader<R> {
89 pub fn new(inner: R, block: u64) -> Self {
92 let len = inner.len();
93 Self {
94 inner,
95 block: block.max(4096),
96 len,
97 cap: DEFAULT_CACHE_CAP,
98 cache: Mutex::new(CacheState {
99 map: HashMap::new(),
100 used: 0,
101 tick: 0,
102 }),
103 }
104 }
105
106 pub fn with_cache_cap(mut self, cap: u64) -> Self {
111 self.cap = cap;
112 self
113 }
114
115 pub fn cached_bytes(&self) -> u64 {
117 self.cache.lock().unwrap().used
118 }
119
120 fn bounds(&self, offset: u64, len: u64) -> std::io::Result<()> {
121 if offset.checked_add(len).is_none_or(|e| e > self.len) {
122 return Err(std::io::Error::new(
123 std::io::ErrorKind::UnexpectedEof,
124 "range out of bounds",
125 ));
126 }
127 Ok(())
128 }
129
130 fn ensure(&self, want: &BTreeSet<u64>) -> std::io::Result<()> {
134 let missing: Vec<u64> = {
135 let mut st = self.cache.lock().unwrap();
136 st.tick += 1;
137 let tick = st.tick;
138 want.iter()
139 .copied()
140 .filter(|b| match st.map.get_mut(b) {
141 Some(e) => {
142 e.stamp = tick;
143 false
144 }
145 None => true,
146 })
147 .collect()
148 };
149 if missing.is_empty() {
150 return Ok(());
151 }
152 let mut spans: Vec<(u64, u64)> = Vec::new();
154 let mut runs: Vec<(u64, u64)> = Vec::new();
155 let mut i = 0;
156 while i < missing.len() {
157 let first = missing[i];
158 let mut last = first;
159 let mut j = i + 1;
160 while j < missing.len() && missing[j] == last + 1 {
161 last = missing[j];
162 j += 1;
163 }
164 let off = first * self.block;
165 let end = ((last + 1) * self.block).min(self.len);
166 spans.push((off, end - off));
167 runs.push((first, last));
168 i = j;
169 }
170 let blobs = self.inner.read_many(&spans)?;
171 if blobs.len() != spans.len() {
172 return Err(std::io::Error::other("block fetch returned wrong count"));
173 }
174 let mut st = self.cache.lock().unwrap();
175 st.tick += 1;
176 let tick = st.tick;
177 for (&(first, last), blob) in runs.iter().zip(blobs.into_iter()) {
178 let span_start = first * self.block;
179 for b in first..=last {
180 let lo = (b * self.block - span_start) as usize;
181 let hi = ((((b + 1) * self.block).min(self.len)) - span_start) as usize;
182 let hi = hi.min(blob.len());
183 let lo = lo.min(hi);
184 let data: Arc<[u8]> = Arc::from(&blob[lo..hi]);
185 st.used += data.len() as u64;
186 if let Some(old) = st.map.insert(b, CacheEntry { data, stamp: tick }) {
187 st.used -= old.data.len() as u64; }
189 }
190 }
191 Ok(())
192 }
193
194 fn trim(&self) {
198 let mut st = self.cache.lock().unwrap();
199 if st.used <= self.cap {
200 return;
201 }
202 let mut order: Vec<(u64, u64)> = st.map.iter().map(|(&b, e)| (e.stamp, b)).collect();
203 order.sort_unstable();
204 for (_, b) in order {
205 if st.used <= self.cap {
206 break;
207 }
208 if let Some(e) = st.map.remove(&b) {
209 st.used -= e.data.len() as u64;
210 }
211 }
212 }
213
214 fn assemble(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
219 let first = offset / self.block;
220 let last = (offset + len - 1) / self.block;
221 let resident: Vec<Option<Arc<[u8]>>> = {
222 let st = self.cache.lock().unwrap();
223 (first..=last)
224 .map(|b| st.map.get(&b).map(|e| e.data.clone()))
225 .collect()
226 };
227 let mut out = Vec::with_capacity(len as usize);
228 let mut pos = offset;
229 let end = offset + len;
230 while pos < end {
231 let b = pos / self.block;
232 let block_start = b * self.block;
233 let within = (pos - block_start) as usize;
234 let fetched: Vec<u8>;
235 let blk: &[u8] = match &resident[(b - first) as usize] {
236 Some(data) => data,
237 None => {
238 let blen = ((b + 1) * self.block).min(self.len) - block_start;
239 fetched = self.inner.read_at(block_start, blen)?;
240 &fetched
241 }
242 };
243 let take = ((end - pos) as usize).min(blk.len().saturating_sub(within));
244 if take == 0 {
245 break;
246 }
247 out.extend_from_slice(&blk[within..within + take]);
248 pos += take as u64;
249 }
250 Ok(out)
251 }
252}
253
254impl<R: RangeReader> RangeReader for BlockCacheReader<R> {
255 fn len(&self) -> u64 {
256 self.len
257 }
258
259 fn concurrency(&self) -> usize {
260 self.inner.concurrency()
261 }
262
263 fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
264 if len == 0 {
265 return Ok(Vec::new());
266 }
267 self.bounds(offset, len)?;
268 let want: BTreeSet<u64> = (offset / self.block..=(offset + len - 1) / self.block).collect();
269 self.ensure(&want)?;
270 let out = self.assemble(offset, len)?;
271 self.trim();
272 Ok(out)
273 }
274
275 fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
276 let mut want = BTreeSet::new();
277 for &(o, l) in ranges {
278 if l == 0 {
279 continue;
280 }
281 self.bounds(o, l)?;
282 for b in o / self.block..=(o + l - 1) / self.block {
283 want.insert(b);
284 }
285 }
286 self.ensure(&want)?;
287 let out = ranges
288 .iter()
289 .map(|&(o, l)| {
290 if l == 0 {
291 Ok(Vec::new())
292 } else {
293 self.assemble(o, l)
294 }
295 })
296 .collect::<std::io::Result<Vec<_>>>()?;
297 self.trim();
298 Ok(out)
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use crate::reader::{CountingReader, SliceReader};
306
307 #[test]
308 fn caches_blocks_and_serves_exact_bytes() {
309 let data: Vec<u8> = (0..100_000u32).map(|i| i as u8).collect();
310 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
311 let r = BlockCacheReader::new(counting.clone(), 16 * 1024);
312
313 for off in [10u64, 200, 5000, 16_500, 17_000, 33_000, 33_100] {
315 assert_eq!(
316 r.read_at(off, 32).unwrap(),
317 data[off as usize..off as usize + 32]
318 );
319 }
320 assert!(
323 counting.requests() <= 3,
324 "physical fetches: {}",
325 counting.requests()
326 );
327
328 let before = counting.requests();
330 assert_eq!(r.read_at(200, 16).unwrap(), data[200..216]);
331 assert_eq!(counting.requests(), before);
332 }
333
334 #[test]
335 fn read_many_fetches_missing_blocks_once() {
336 let data: Vec<u8> = (0..200_000u32).map(|i| (i * 7) as u8).collect();
337 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
338 let r = BlockCacheReader::new(counting.clone(), 32 * 1024);
339 let ranges = [(0u64, 8u64), (40_000, 16), (40_050, 16), (130_000, 64)];
340 let out = r.read_many(&ranges).unwrap();
341 for (&(o, l), got) in ranges.iter().zip(&out) {
342 assert_eq!(got, &data[o as usize..(o + l) as usize]);
343 }
344 assert!(
346 counting.requests() <= 3,
347 "physical: {}",
348 counting.requests()
349 );
350 }
351
352 #[test]
353 fn out_of_bounds_errors() {
354 let data = vec![0u8; 1000];
355 let r = BlockCacheReader::new(SliceReader::new(&data), 8192);
356 assert!(r.read_at(990, 20).is_err());
357 }
358
359 #[test]
362 fn eviction_caps_resident_bytes_and_keeps_reads_exact() {
363 let data: Vec<u8> = (0..1_000_000u32).map(|i| (i * 31) as u8).collect();
364 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
365 let cap = 64 * 1024;
367 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(cap);
368 for off in (0..1_000_000u64 - 64).step_by(37_777) {
369 assert_eq!(
370 r.read_at(off, 64).unwrap(),
371 data[off as usize..off as usize + 64]
372 );
373 assert!(
374 r.cached_bytes() <= cap,
375 "resident {} > cap {cap}",
376 r.cached_bytes()
377 );
378 }
379 }
380
381 #[test]
384 fn eviction_is_lru() {
385 let data: Vec<u8> = (0..64 * 1024u32).map(|i| i as u8).collect();
386 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
387 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(16 * 1024);
389 let block = |i: u64| i * 8192;
390 r.read_at(block(0), 16).unwrap(); r.read_at(block(1), 16).unwrap(); r.read_at(block(0), 16).unwrap(); let before = counting.requests();
395 r.read_at(block(2), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
397
398 let before = counting.requests();
399 r.read_at(block(0), 16).unwrap(); assert_eq!(
401 counting.requests(),
402 before,
403 "recently-touched block evicted"
404 );
405
406 let before = counting.requests();
407 r.read_at(block(1), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
409 }
410
411 #[test]
415 fn request_larger_than_cap_reads_exactly_then_trims() {
416 let data: Vec<u8> = (0..256 * 1024u32).map(|i| (i * 7) as u8).collect();
417 let cap = 16 * 1024;
418 let r = BlockCacheReader::new(SliceReader::new(&data), 8192).with_cache_cap(cap);
419 let (off, len) = (1000usize, 96 * 1024usize); let out = r.read_at(off as u64, len as u64).unwrap();
421 assert_eq!(out, &data[off..off + len]);
422 assert!(
423 r.cached_bytes() <= cap,
424 "resident {} > cap {cap} after the read",
425 r.cached_bytes()
426 );
427 }
428}