use std::borrow::Cow;
use std::io;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use aligned_vec::{AVec, RuntimeAlign};
use super::{BLOCK_SIZE, BlockId, BlockOffset, BlockRequest, CacheController, CacheRead, FileId};
use crate::common::ext::aligned_vec::ACow;
#[derive(Debug)]
pub struct CachedSlice {
pub(crate) path: PathBuf,
file_id: FileId,
len_bytes: usize,
pub(crate) controller: Arc<CacheController>,
}
impl CachedSlice {
pub fn open(controller: &Arc<CacheController>, path: &Path) -> io::Result<Self> {
let (file_id, len) = controller.open_file(path)?;
Ok(Self {
path: path.to_path_buf(),
file_id,
len_bytes: len,
controller: Arc::clone(controller),
})
}
pub fn get_range<T: bytemuck::Pod>(&self, range: Range<usize>) -> io::Result<Cow<'_, [T]>> {
let t_size = size_of::<T>();
debug_assert!(t_size != 0, "cannot use zero-sized type");
let byte_range = range.start * t_size..range.end * t_size;
let cow_bytes = self.get_range_bytes(byte_range, align_of::<T>())?;
Ok(cow_bytes.try_cast_bytemuck().unwrap())
}
pub fn get_range_bytes(&self, range: Range<usize>, align: usize) -> io::Result<ACow<'_>> {
debug_assert!(range.end <= self.len_bytes);
if range.is_empty() {
return Ok(ACow::Borrowed(&[]));
}
let mut blocks_iter = self.blocks_for(range.clone());
if blocks_iter.len() == 1 {
let req = blocks_iter.next().expect("We just checked len() == 1");
let result = self.controller.get_from_cache(req, |bytes| {
AVec::<u8, RuntimeAlign>::from_slice(align, bytes)
})?;
return Ok(match result {
CacheRead::Hit(bytes) => ACow::Borrowed(bytes),
CacheRead::Miss(buf) => ACow::Owned(buf),
});
}
let mut buf = AVec::with_capacity(align, range.len());
let mut copy_block = |slice: &[u8]| buf.extend_from_slice(slice);
for req in blocks_iter {
let read = self.controller.get_from_cache(req, &mut copy_block)?;
if let CacheRead::Hit(slice) = read {
copy_block(slice);
}
}
Ok(ACow::Owned(buf))
}
pub fn populate(&self) -> io::Result<()> {
if self.len_bytes == 0 {
return Ok(());
}
if crate::common::low_memory::low_memory_mode().skip_populate() {
return Ok(());
}
let num_blocks = self.len_bytes.div_ceil(BLOCK_SIZE);
for block_idx in 0..num_blocks {
let req = BlockRequest {
key: BlockId {
file_id: self.file_id,
offset: BlockOffset(
u32::try_from(block_idx).expect("file too large disk cache"),
),
},
range: 0..1,
};
self.controller.get_from_cache(req, |_| ())?;
}
Ok(())
}
#[cfg(test)]
pub fn get<T: bytemuck::Pod>(&self, idx: usize) -> io::Result<Cow<'_, T>> {
let slice = self.get_range::<T>(idx..idx + 1)?;
let cow = match slice {
Cow::Borrowed(slice) => Cow::Borrowed(&slice[0]),
Cow::Owned(mut vec) => Cow::Owned(vec.pop().unwrap()),
};
Ok(cow)
}
pub fn len<T>(&self) -> usize {
self.len_bytes / size_of::<T>()
}
fn blocks_for(&self, bytes_range: Range<usize>) -> impl ExactSizeIterator<Item = BlockRequest> {
debug_assert!(bytes_range.start <= bytes_range.end);
debug_assert!(bytes_range.end <= self.len_bytes);
debug_assert!(!bytes_range.is_empty(), "empty range would underflow");
blocks_for_range_in_file(self.file_id, bytes_range)
}
}
#[inline(always)]
fn blocks_for_range_in_file(
file_id: FileId,
bytes_range: Range<usize>,
) -> impl ExactSizeIterator<Item = BlockRequest> {
let first_block = bytes_range.start / BLOCK_SIZE;
let leading_offset = bytes_range.start - (first_block * BLOCK_SIZE);
let last_block = (bytes_range.end - 1) / BLOCK_SIZE;
let trailing_offset = bytes_range.end - (last_block * BLOCK_SIZE);
(first_block..last_block + 1).map(move |block_offset| {
let block_id = BlockId {
file_id,
offset: BlockOffset(
u32::try_from(block_offset).expect("file too large for block cache (>70 TiB)"),
),
};
let range_start = if block_offset == first_block {
leading_offset
} else {
0
};
let range_end = if block_offset == last_block {
trailing_offset
} else {
BLOCK_SIZE
};
BlockRequest {
key: block_id,
range: range_start..range_end,
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_request_calculation() {
let file_id = FileId(0);
let file_len = BLOCK_SIZE * 10 + 100;
let blocks: Vec<_> = blocks_for_range_in_file(file_id, 0..100).collect();
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0].key.offset.0, 0);
assert_eq!(blocks[0].range, 0..100);
let blocks: Vec<_> =
blocks_for_range_in_file(file_id, BLOCK_SIZE - 50..BLOCK_SIZE + 50).collect();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].key.offset.0, 0);
assert_eq!(blocks[0].range, (BLOCK_SIZE - 50)..BLOCK_SIZE);
assert_eq!(blocks[1].key.offset.0, 1);
assert_eq!(blocks[1].range, 0..50);
let blocks: Vec<_> =
blocks_for_range_in_file(file_id, BLOCK_SIZE * 2..BLOCK_SIZE * 4).collect();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].key.offset.0, 2);
assert_eq!(blocks[0].range, 0..BLOCK_SIZE);
assert_eq!(blocks[1].key.offset.0, 3);
assert_eq!(blocks[1].range, 0..BLOCK_SIZE);
let blocks: Vec<_> =
blocks_for_range_in_file(file_id, BLOCK_SIZE * 9 + 50..file_len).collect();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].key.offset.0, 9);
assert_eq!(blocks[0].range, 50..BLOCK_SIZE);
assert_eq!(blocks[1].key.offset.0, 10);
assert_eq!(blocks[1].range, 0..100);
}
}