1use crate::reader::RangeReader;
28use std::collections::{BTreeSet, HashMap, HashSet};
29use std::sync::{Arc, Condvar, 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
66fn charge(data: &Arc<[u8]>) -> u64 {
70 crate::chunk_cache::alloc_footprint(data.len() as u64 + 16)
71}
72
73struct CacheEntry {
75 data: Arc<[u8]>,
76 stamp: u64,
77}
78
79struct CacheState {
82 map: HashMap<u64, CacheEntry>,
83 used: u64,
84 tick: u64,
85 in_flight: HashSet<u64>,
89}
90
91pub struct BlockCacheReader<R> {
92 inner: R,
93 block: u64,
94 len: u64,
95 cap: u64,
96 cache: Mutex<CacheState>,
97 landed: Condvar,
100}
101
102impl<R: RangeReader> BlockCacheReader<R> {
103 pub fn new(inner: R, block: u64) -> Self {
106 let len = inner.len();
107 Self {
108 inner,
109 block: block.max(4096),
110 len,
111 cap: DEFAULT_CACHE_CAP,
112 cache: Mutex::new(CacheState {
113 map: HashMap::new(),
114 used: 0,
115 tick: 0,
116 in_flight: HashSet::new(),
117 }),
118 landed: Condvar::new(),
119 }
120 }
121
122 pub fn with_cache_cap(mut self, cap: u64) -> Self {
127 self.cap = cap;
128 self
129 }
130
131 pub fn cached_bytes(&self) -> u64 {
133 self.cache.lock().unwrap().used
134 }
135
136 fn bounds(&self, offset: u64, len: u64) -> std::io::Result<()> {
137 if offset.checked_add(len).is_none_or(|e| e > self.len) {
138 return Err(std::io::Error::new(
143 std::io::ErrorKind::UnexpectedEof,
144 format!(
145 "range out of bounds: read {offset}+{len} exceeds the reported resource length {} — \
146 the file's layout expects more bytes than the length probe found \
147 (a host serving a compressed Content-Length can cause this)",
148 self.len
149 ),
150 ));
151 }
152 Ok(())
153 }
154
155 fn ensure(&self, want: &BTreeSet<u64>) -> std::io::Result<()> {
159 let (missing, others): (Vec<u64>, Vec<u64>) = {
163 let mut st = self.cache.lock().unwrap();
164 st.tick += 1;
165 let tick = st.tick;
166 let mut missing = Vec::new();
167 let mut others = Vec::new();
168 for &b in want {
169 if let Some(e) = st.map.get_mut(&b) {
170 e.stamp = tick;
171 } else if st.in_flight.contains(&b) {
172 others.push(b);
173 } else {
174 missing.push(b);
175 }
176 }
177 for &b in &missing {
178 st.in_flight.insert(b);
179 }
180 (missing, others)
181 };
182 let fetched = self.fetch_claimed(&missing);
183 if !others.is_empty() {
184 let mut st = self.cache.lock().unwrap();
185 while others.iter().any(|b| st.in_flight.contains(b)) {
186 st = self.landed.wait(st).unwrap();
187 }
188 }
191 fetched
192 }
193
194 fn fetch_claimed(&self, missing: &[u64]) -> std::io::Result<()> {
197 if missing.is_empty() {
198 return Ok(());
199 }
200 let r = self.fetch_missing(missing);
201 let mut st = self.cache.lock().unwrap();
202 for b in missing {
203 st.in_flight.remove(b);
204 }
205 drop(st);
206 self.landed.notify_all();
207 r
208 }
209
210 fn fetch_missing(&self, missing: &[u64]) -> std::io::Result<()> {
211 let mut spans: Vec<(u64, u64)> = Vec::new();
213 let mut runs: Vec<(u64, u64)> = Vec::new();
214 let mut i = 0;
215 while i < missing.len() {
216 let first = missing[i];
217 let mut last = first;
218 let mut j = i + 1;
219 while j < missing.len() && missing[j] == last + 1 {
220 last = missing[j];
221 j += 1;
222 }
223 let off = first * self.block;
224 let end = ((last + 1) * self.block).min(self.len);
225 spans.push((off, end - off));
226 runs.push((first, last));
227 i = j;
228 }
229 let blobs = self.inner.read_many(&spans)?;
230 if blobs.len() != spans.len() {
231 return Err(std::io::Error::other("block fetch returned wrong count"));
232 }
233 if let Some(((o, l), b)) = spans
236 .iter()
237 .zip(&blobs)
238 .find(|((_, l), b)| b.len() as u64 != *l)
239 {
240 return Err(std::io::Error::new(
241 std::io::ErrorKind::UnexpectedEof,
242 format!("block fetch at {o} returned {} bytes, not {l}", b.len()),
243 ));
244 }
245 let mut st = self.cache.lock().unwrap();
246 st.tick += 1;
247 let tick = st.tick;
248 for (&(first, last), blob) in runs.iter().zip(blobs.into_iter()) {
249 let span_start = first * self.block;
250 for b in first..=last {
251 let lo = (b * self.block - span_start) as usize;
252 let hi = ((((b + 1) * self.block).min(self.len)) - span_start) as usize;
253 let hi = hi.min(blob.len());
254 let lo = lo.min(hi);
255 let data: Arc<[u8]> = Arc::from(&blob[lo..hi]);
256 st.used += charge(&data);
257 if let Some(old) = st.map.insert(b, CacheEntry { data, stamp: tick }) {
258 st.used -= charge(&old.data); }
260 }
261 }
262 Ok(())
263 }
264
265 fn trim(&self) {
275 let mut st = self.cache.lock().unwrap();
276 if st.used <= self.cap {
277 return;
278 }
279 let slack = if self.cap / 8 >= 4 * self.block {
280 self.cap / 8
281 } else {
282 0
283 };
284 let target = self.cap - slack;
285 let mut order: Vec<(u64, u64)> = st.map.iter().map(|(&b, e)| (e.stamp, b)).collect();
286 order.sort_unstable();
287 for (_, b) in order {
288 if st.used <= target {
289 break;
290 }
291 if let Some(e) = st.map.remove(&b) {
292 st.used -= charge(&e.data);
293 }
294 }
295 }
296
297 fn assemble(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
302 let first = offset / self.block;
303 let last = (offset + len - 1) / self.block;
304 let resident: Vec<Option<Arc<[u8]>>> = {
305 let st = self.cache.lock().unwrap();
306 (first..=last)
307 .map(|b| st.map.get(&b).map(|e| e.data.clone()))
308 .collect()
309 };
310 let mut out = Vec::with_capacity(len as usize);
311 let mut pos = offset;
312 let end = offset + len;
313 while pos < end {
314 let b = pos / self.block;
315 let block_start = b * self.block;
316 let within = (pos - block_start) as usize;
317 let fetched: Vec<u8>;
318 let blk: &[u8] = match &resident[(b - first) as usize] {
319 Some(data) => data,
320 None => {
321 let blen = ((b + 1) * self.block).min(self.len) - block_start;
322 fetched = self.inner.read_at(block_start, blen)?;
323 &fetched
324 }
325 };
326 let take = ((end - pos) as usize).min(blk.len().saturating_sub(within));
327 if take == 0 {
328 break;
329 }
330 out.extend_from_slice(&blk[within..within + take]);
331 pos += take as u64;
332 }
333 if out.len() as u64 != len {
334 return Err(std::io::Error::new(
335 std::io::ErrorKind::UnexpectedEof,
336 format!("short read: {} of {len} bytes at {offset}", out.len()),
337 ));
338 }
339 Ok(out)
340 }
341}
342
343impl<R: RangeReader> RangeReader for BlockCacheReader<R> {
344 fn len(&self) -> u64 {
345 self.len
346 }
347
348 fn concurrency(&self) -> usize {
349 self.inner.concurrency()
350 }
351
352 fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
353 if len == 0 {
354 return Ok(Vec::new());
355 }
356 self.bounds(offset, len)?;
357 let want: BTreeSet<u64> = (offset / self.block..=(offset + len - 1) / self.block).collect();
358 self.ensure(&want)?;
359 let out = self.assemble(offset, len)?;
360 self.trim();
361 Ok(out)
362 }
363
364 fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
365 let mut want = BTreeSet::new();
366 for &(o, l) in ranges {
367 if l == 0 {
368 continue;
369 }
370 self.bounds(o, l)?;
371 for b in o / self.block..=(o + l - 1) / self.block {
372 want.insert(b);
373 }
374 }
375 self.ensure(&want)?;
376 let out = ranges
377 .iter()
378 .map(|&(o, l)| {
379 if l == 0 {
380 Ok(Vec::new())
381 } else {
382 self.assemble(o, l)
383 }
384 })
385 .collect::<std::io::Result<Vec<_>>>()?;
386 self.trim();
387 Ok(out)
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use crate::reader::{CountingReader, SliceReader};
395
396 #[test]
397 fn caches_blocks_and_serves_exact_bytes() {
398 let data: Vec<u8> = (0..100_000u32).map(|i| i as u8).collect();
399 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
400 let r = BlockCacheReader::new(counting.clone(), 16 * 1024);
401
402 for off in [10u64, 200, 5000, 16_500, 17_000, 33_000, 33_100] {
404 assert_eq!(
405 r.read_at(off, 32).unwrap(),
406 data[off as usize..off as usize + 32]
407 );
408 }
409 assert!(
412 counting.requests() <= 3,
413 "physical fetches: {}",
414 counting.requests()
415 );
416
417 let before = counting.requests();
419 assert_eq!(r.read_at(200, 16).unwrap(), data[200..216]);
420 assert_eq!(counting.requests(), before);
421 }
422
423 #[test]
424 fn read_many_fetches_missing_blocks_once() {
425 let data: Vec<u8> = (0..200_000u32).map(|i| (i * 7) as u8).collect();
426 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
427 let r = BlockCacheReader::new(counting.clone(), 32 * 1024);
428 let ranges = [(0u64, 8u64), (40_000, 16), (40_050, 16), (130_000, 64)];
429 let out = r.read_many(&ranges).unwrap();
430 for (&(o, l), got) in ranges.iter().zip(&out) {
431 assert_eq!(got, &data[o as usize..(o + l) as usize]);
432 }
433 assert!(
435 counting.requests() <= 3,
436 "physical: {}",
437 counting.requests()
438 );
439 }
440
441 struct Slow(Vec<u8>, std::sync::atomic::AtomicUsize);
443 impl RangeReader for Slow {
444 fn len(&self) -> u64 {
445 self.0.len() as u64
446 }
447 fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
448 self.1.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
449 std::thread::sleep(std::time::Duration::from_millis(30));
450 Ok(self.0[offset as usize..(offset + len) as usize].to_vec())
451 }
452 }
453
454 #[test]
458 fn concurrent_readers_fetch_a_block_once() {
459 let r = BlockCacheReader::new(Slow(vec![3u8; 100_000], Default::default()), 4096);
460 std::thread::scope(|s| {
461 for _ in 0..6 {
462 s.spawn(|| assert_eq!(r.read_at(5000, 100).unwrap(), vec![3u8; 100]));
463 }
464 });
465 assert_eq!(r.inner.1.load(std::sync::atomic::Ordering::SeqCst), 1);
466 }
467
468 struct Short(Vec<u8>);
470 impl RangeReader for Short {
471 fn len(&self) -> u64 {
472 self.0.len() as u64
473 }
474 fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
475 let end = (offset + len).saturating_sub(1) as usize;
476 Ok(self.0[offset as usize..end].to_vec())
477 }
478 }
479
480 #[test]
483 fn a_short_blob_is_refused_not_cached() {
484 let r = BlockCacheReader::new(Short(vec![9u8; 10_000]), 4096);
485 let err = r.read_at(100, 50).unwrap_err();
486 assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof, "{err}");
487 assert!(r.read_many(&[(0, 10), (5000, 10)]).is_err());
488 }
489
490 #[test]
491 fn out_of_bounds_errors() {
492 let data = vec![0u8; 1000];
493 let r = BlockCacheReader::new(SliceReader::new(&data), 8192);
494 assert!(r.read_at(990, 20).is_err());
495 }
496
497 #[test]
500 fn eviction_caps_resident_bytes_and_keeps_reads_exact() {
501 let data: Vec<u8> = (0..1_000_000u32).map(|i| (i * 31) as u8).collect();
502 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
503 let cap = 64 * 1024;
505 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(cap);
506 for off in (0..1_000_000u64 - 64).step_by(37_777) {
507 assert_eq!(
508 r.read_at(off, 64).unwrap(),
509 data[off as usize..off as usize + 64]
510 );
511 assert!(
512 r.cached_bytes() <= cap,
513 "resident {} > cap {cap}",
514 r.cached_bytes()
515 );
516 }
517 }
518
519 #[test]
522 fn eviction_is_lru() {
523 let data: Vec<u8> = (0..64 * 1024u32).map(|i| i as u8).collect();
524 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
525 let two = 2 * charge(&Arc::from(vec![0u8; 8192]));
527 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(two);
528 let block = |i: u64| i * 8192;
529 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();
534 r.read_at(block(2), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
536
537 let before = counting.requests();
538 r.read_at(block(0), 16).unwrap(); assert_eq!(
540 counting.requests(),
541 before,
542 "recently-touched block evicted"
543 );
544
545 let before = counting.requests();
546 r.read_at(block(1), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
548 }
549
550 #[test]
554 fn request_larger_than_cap_reads_exactly_then_trims() {
555 let data: Vec<u8> = (0..256 * 1024u32).map(|i| (i * 7) as u8).collect();
556 let cap = 16 * 1024;
557 let r = BlockCacheReader::new(SliceReader::new(&data), 8192).with_cache_cap(cap);
558 let (off, len) = (1000usize, 96 * 1024usize); let out = r.read_at(off as u64, len as u64).unwrap();
560 assert_eq!(out, &data[off..off + len]);
561 assert!(
562 r.cached_bytes() <= cap,
563 "resident {} > cap {cap} after the read",
564 r.cached_bytes()
565 );
566 }
567}