use std::cell::UnsafeCell;
use std::io::ErrorKind;
use std::ops::Range;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use fs_err as fs;
use parking_lot::Mutex;
use roaring::RoaringBitmap;
use crate::common::generic_consts::AccessPattern;
use crate::common::universal_io::simple_disk_cache::BLOCK_SIZE;
use crate::common::universal_io::{
MmapFile, MmapFs, OpenOptions, Populate, Result, UniversalIoError, UniversalRead,
UniversalReadFs, UniversalWrite, mmap as mmap_file,
};
#[derive(Debug)]
pub(super) struct LocalState {
pub mmap: UnsafeCell<MmapFile>,
pub fetched: Mutex<RoaringBitmap>,
pub fully_populated: AtomicBool,
}
unsafe impl Sync for LocalState {}
impl LocalState {
pub(super) fn new(
local_path: impl AsRef<Path>,
len: u64,
options: OpenOptions,
) -> Result<Self> {
if let Some(parent) = local_path.as_ref().parent() {
fs::create_dir_all(parent)?;
}
let OpenOptions {
writeable: _, need_sequential,
populate: _, advice,
} = options;
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(local_path.as_ref())?;
file.set_len(len)?;
let mmap = MmapFs.open(
local_path.as_ref(),
OpenOptions {
writeable: true,
need_sequential,
populate: Populate::No,
advice,
},
(),
)?;
Ok(LocalState {
mmap: UnsafeCell::new(mmap),
fetched: Mutex::new(RoaringBitmap::new()),
fully_populated: AtomicBool::new(false),
})
}
pub(super) fn resize(&mut self, local_path: impl AsRef<Path>, new_len: u64) -> Result<()> {
let mmap = self.mmap.get_mut();
let current_len = mmap.len::<u8>()?;
if current_len == new_len {
return Ok(());
}
if current_len > new_len {
return Err(UniversalIoError::Io(std::io::Error::new(
ErrorKind::Unsupported,
"Shrinking the file is not supported",
)));
}
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(local_path.as_ref())?;
file.set_len(new_len)?;
mmap.reopen()?;
self.fully_populated.store(false, Ordering::Release);
if current_len % BLOCK_SIZE as u64 != 0 {
let partial_tail_block = (current_len / BLOCK_SIZE as u64) as u32;
self.fetched.lock().remove(partial_tail_block);
}
Ok(())
}
pub(super) fn mmap(&self) -> &MmapFile {
unsafe { self.mmap.get().as_ref_unchecked() }
}
pub(super) fn contains(&self, blocks_range: Range<u32>) -> bool {
if self.fully_populated.load(Ordering::Acquire) {
return true;
}
self.fetched.lock().contains_range(blocks_range)
}
pub(super) unsafe fn read_mmap_bytes<P: AccessPattern>(
&self,
range: Range<u64>,
) -> Result<&[u8]> {
let mmap_bytes = self.mmap().as_bytes::<P>();
mmap_file::read_bytes(mmap_bytes, range)
}
pub(super) unsafe fn write_mmap_bytes(&self, bytes: &[u8], blocks_range: Range<u32>) {
let mmap = unsafe { self.mmap.get().as_mut_unchecked() };
if self.fully_populated.load(Ordering::Acquire) {
return;
}
let mut fetched = self.fetched.lock();
if fetched.contains_range(blocks_range.clone()) {
return;
}
let byte_offset = (blocks_range.start as usize * BLOCK_SIZE) as u64;
let max_len = mmap
.len::<u8>()
.expect("MmapFile::len is infallible")
.saturating_sub(byte_offset);
assert_eq!(
bytes.len() as u64,
max_len.min((blocks_range.len() * BLOCK_SIZE) as u64)
);
mmap.write(byte_offset, bytes)
.expect("MmapFile::write is infallible");
fetched.insert_range(blocks_range);
let total_blocks = mmap
.len::<u8>()
.expect("MmapFile::len is infallible")
.div_ceil(BLOCK_SIZE as u64);
if fetched.len() == total_blocks {
self.fully_populated.store(true, Ordering::Release);
}
}
}