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(
127 std::io::ErrorKind::UnexpectedEof,
128 format!(
129 "range out of bounds: read {offset}+{len} exceeds the reported resource length {} — \
130 the file's layout expects more bytes than the length probe found \
131 (a host serving a compressed Content-Length can cause this)",
132 self.len
133 ),
134 ));
135 }
136 Ok(())
137 }
138
139 fn ensure(&self, want: &BTreeSet<u64>) -> std::io::Result<()> {
143 let missing: Vec<u64> = {
144 let mut st = self.cache.lock().unwrap();
145 st.tick += 1;
146 let tick = st.tick;
147 want.iter()
148 .copied()
149 .filter(|b| match st.map.get_mut(b) {
150 Some(e) => {
151 e.stamp = tick;
152 false
153 }
154 None => true,
155 })
156 .collect()
157 };
158 if missing.is_empty() {
159 return Ok(());
160 }
161 let mut spans: Vec<(u64, u64)> = Vec::new();
163 let mut runs: Vec<(u64, u64)> = Vec::new();
164 let mut i = 0;
165 while i < missing.len() {
166 let first = missing[i];
167 let mut last = first;
168 let mut j = i + 1;
169 while j < missing.len() && missing[j] == last + 1 {
170 last = missing[j];
171 j += 1;
172 }
173 let off = first * self.block;
174 let end = ((last + 1) * self.block).min(self.len);
175 spans.push((off, end - off));
176 runs.push((first, last));
177 i = j;
178 }
179 let blobs = self.inner.read_many(&spans)?;
180 if blobs.len() != spans.len() {
181 return Err(std::io::Error::other("block fetch returned wrong count"));
182 }
183 let mut st = self.cache.lock().unwrap();
184 st.tick += 1;
185 let tick = st.tick;
186 for (&(first, last), blob) in runs.iter().zip(blobs.into_iter()) {
187 let span_start = first * self.block;
188 for b in first..=last {
189 let lo = (b * self.block - span_start) as usize;
190 let hi = ((((b + 1) * self.block).min(self.len)) - span_start) as usize;
191 let hi = hi.min(blob.len());
192 let lo = lo.min(hi);
193 let data: Arc<[u8]> = Arc::from(&blob[lo..hi]);
194 st.used += data.len() as u64;
195 if let Some(old) = st.map.insert(b, CacheEntry { data, stamp: tick }) {
196 st.used -= old.data.len() as u64; }
198 }
199 }
200 Ok(())
201 }
202
203 fn trim(&self) {
207 let mut st = self.cache.lock().unwrap();
208 if st.used <= self.cap {
209 return;
210 }
211 let mut order: Vec<(u64, u64)> = st.map.iter().map(|(&b, e)| (e.stamp, b)).collect();
212 order.sort_unstable();
213 for (_, b) in order {
214 if st.used <= self.cap {
215 break;
216 }
217 if let Some(e) = st.map.remove(&b) {
218 st.used -= e.data.len() as u64;
219 }
220 }
221 }
222
223 fn assemble(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
228 let first = offset / self.block;
229 let last = (offset + len - 1) / self.block;
230 let resident: Vec<Option<Arc<[u8]>>> = {
231 let st = self.cache.lock().unwrap();
232 (first..=last)
233 .map(|b| st.map.get(&b).map(|e| e.data.clone()))
234 .collect()
235 };
236 let mut out = Vec::with_capacity(len as usize);
237 let mut pos = offset;
238 let end = offset + len;
239 while pos < end {
240 let b = pos / self.block;
241 let block_start = b * self.block;
242 let within = (pos - block_start) as usize;
243 let fetched: Vec<u8>;
244 let blk: &[u8] = match &resident[(b - first) as usize] {
245 Some(data) => data,
246 None => {
247 let blen = ((b + 1) * self.block).min(self.len) - block_start;
248 fetched = self.inner.read_at(block_start, blen)?;
249 &fetched
250 }
251 };
252 let take = ((end - pos) as usize).min(blk.len().saturating_sub(within));
253 if take == 0 {
254 break;
255 }
256 out.extend_from_slice(&blk[within..within + take]);
257 pos += take as u64;
258 }
259 Ok(out)
260 }
261}
262
263impl<R: RangeReader> RangeReader for BlockCacheReader<R> {
264 fn len(&self) -> u64 {
265 self.len
266 }
267
268 fn concurrency(&self) -> usize {
269 self.inner.concurrency()
270 }
271
272 fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
273 if len == 0 {
274 return Ok(Vec::new());
275 }
276 self.bounds(offset, len)?;
277 let want: BTreeSet<u64> = (offset / self.block..=(offset + len - 1) / self.block).collect();
278 self.ensure(&want)?;
279 let out = self.assemble(offset, len)?;
280 self.trim();
281 Ok(out)
282 }
283
284 fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
285 let mut want = BTreeSet::new();
286 for &(o, l) in ranges {
287 if l == 0 {
288 continue;
289 }
290 self.bounds(o, l)?;
291 for b in o / self.block..=(o + l - 1) / self.block {
292 want.insert(b);
293 }
294 }
295 self.ensure(&want)?;
296 let out = ranges
297 .iter()
298 .map(|&(o, l)| {
299 if l == 0 {
300 Ok(Vec::new())
301 } else {
302 self.assemble(o, l)
303 }
304 })
305 .collect::<std::io::Result<Vec<_>>>()?;
306 self.trim();
307 Ok(out)
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::reader::{CountingReader, SliceReader};
315
316 #[test]
317 fn caches_blocks_and_serves_exact_bytes() {
318 let data: Vec<u8> = (0..100_000u32).map(|i| i as u8).collect();
319 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
320 let r = BlockCacheReader::new(counting.clone(), 16 * 1024);
321
322 for off in [10u64, 200, 5000, 16_500, 17_000, 33_000, 33_100] {
324 assert_eq!(
325 r.read_at(off, 32).unwrap(),
326 data[off as usize..off as usize + 32]
327 );
328 }
329 assert!(
332 counting.requests() <= 3,
333 "physical fetches: {}",
334 counting.requests()
335 );
336
337 let before = counting.requests();
339 assert_eq!(r.read_at(200, 16).unwrap(), data[200..216]);
340 assert_eq!(counting.requests(), before);
341 }
342
343 #[test]
344 fn read_many_fetches_missing_blocks_once() {
345 let data: Vec<u8> = (0..200_000u32).map(|i| (i * 7) as u8).collect();
346 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
347 let r = BlockCacheReader::new(counting.clone(), 32 * 1024);
348 let ranges = [(0u64, 8u64), (40_000, 16), (40_050, 16), (130_000, 64)];
349 let out = r.read_many(&ranges).unwrap();
350 for (&(o, l), got) in ranges.iter().zip(&out) {
351 assert_eq!(got, &data[o as usize..(o + l) as usize]);
352 }
353 assert!(
355 counting.requests() <= 3,
356 "physical: {}",
357 counting.requests()
358 );
359 }
360
361 #[test]
362 fn out_of_bounds_errors() {
363 let data = vec![0u8; 1000];
364 let r = BlockCacheReader::new(SliceReader::new(&data), 8192);
365 assert!(r.read_at(990, 20).is_err());
366 }
367
368 #[test]
371 fn eviction_caps_resident_bytes_and_keeps_reads_exact() {
372 let data: Vec<u8> = (0..1_000_000u32).map(|i| (i * 31) as u8).collect();
373 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
374 let cap = 64 * 1024;
376 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(cap);
377 for off in (0..1_000_000u64 - 64).step_by(37_777) {
378 assert_eq!(
379 r.read_at(off, 64).unwrap(),
380 data[off as usize..off as usize + 64]
381 );
382 assert!(
383 r.cached_bytes() <= cap,
384 "resident {} > cap {cap}",
385 r.cached_bytes()
386 );
387 }
388 }
389
390 #[test]
393 fn eviction_is_lru() {
394 let data: Vec<u8> = (0..64 * 1024u32).map(|i| i as u8).collect();
395 let counting = Arc::new(CountingReader::new(SliceReader::new(&data)));
396 let r = BlockCacheReader::new(counting.clone(), 8192).with_cache_cap(16 * 1024);
398 let block = |i: u64| i * 8192;
399 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();
404 r.read_at(block(2), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
406
407 let before = counting.requests();
408 r.read_at(block(0), 16).unwrap(); assert_eq!(
410 counting.requests(),
411 before,
412 "recently-touched block evicted"
413 );
414
415 let before = counting.requests();
416 r.read_at(block(1), 16).unwrap(); assert_eq!(counting.requests(), before + 1);
418 }
419
420 #[test]
424 fn request_larger_than_cap_reads_exactly_then_trims() {
425 let data: Vec<u8> = (0..256 * 1024u32).map(|i| (i * 7) as u8).collect();
426 let cap = 16 * 1024;
427 let r = BlockCacheReader::new(SliceReader::new(&data), 8192).with_cache_cap(cap);
428 let (off, len) = (1000usize, 96 * 1024usize); let out = r.read_at(off as u64, len as u64).unwrap();
430 assert_eq!(out, &data[off..off + len]);
431 assert!(
432 r.cached_bytes() <= cap,
433 "resident {} > cap {cap} after the read",
434 r.cached_bytes()
435 );
436 }
437}