use std::borrow::Cow;
use std::fmt::Debug;
use std::io::{self, ErrorKind};
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use parking_lot::Mutex;
use super::BLOCK_SIZE;
use super::fs::DiskCacheFs;
use crate::common::generic_consts::{AccessPattern, Sequential};
use crate::common::mmap::AdviceSetting;
use crate::common::universal_io::simple_disk_cache::local_state::LocalState;
use crate::common::universal_io::simple_disk_cache::pipeline::{DiskCachePipeline, OwnedDiskCachePipeline};
use crate::common::universal_io::{
Item, OpenOptions, OwnedReadPipeline, Populate, ReadRange, Result, UniversalIoError,
UniversalKind, UniversalRead, UniversalReadFs, UserData,
};
pub struct DiskCache<R>
where
R: UniversalRead,
{
remote_fs: R::Fs,
remote_extra: <R::Fs as UniversalReadFs>::OpenExtra,
remote_path: PathBuf,
remote: OnceLock<R>,
pub(super) open_options: OpenOptions,
pub(super) local_path: PathBuf,
pub(super) local: OnceLock<LocalState>,
init_lock: Arc<Mutex<InitSource<R>>>,
}
impl<R> Debug for DiskCache<R>
where
R: UniversalRead,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiskCache")
.field("remote_path", &self.remote_path)
.field("remote", &self.remote)
.field("open_options", &self.open_options)
.field("local_path", &self.local_path)
.field("local", &self.local)
.finish_non_exhaustive()
}
}
pub(super) enum InitSource<R: UniversalRead> {
FromScratch,
FromPrefiller(R::OwnedReadPipeline<u8, Range<u32>>),
}
impl<R: UniversalRead> InitSource<R> {
pub(super) fn from_prefiller(pipe: R::OwnedReadPipeline<u8, Range<u32>>) -> Self {
Self::FromPrefiller(pipe)
}
}
impl<R> DiskCache<R>
where
R: UniversalRead + Clone,
R::Fs: Clone + Send + Sync,
<R::Fs as UniversalReadFs>::OpenExtra: Clone + Send + Sync,
R::OwnedReadPipeline<u8, Range<u32>>: Send,
{
pub(super) fn new(
remote_fs: R::Fs,
remote_extra: <R::Fs as UniversalReadFs>::OpenExtra,
remote_path: impl AsRef<Path>,
local_path: PathBuf,
options: OpenOptions,
init_source: InitSource<R>,
) -> Self {
Self {
remote_fs,
remote_extra,
remote_path: remote_path.as_ref().to_owned(),
remote: OnceLock::new(),
open_options: options,
local_path,
local: OnceLock::new(),
init_lock: Arc::new(Mutex::new(init_source)),
}
}
pub(super) fn remote(&self) -> Result<&R> {
if let Some(r) = self.remote.get() {
return Ok(r);
}
let remote_options = OpenOptions {
writeable: false,
populate: Populate::No,
need_sequential: false,
advice: AdviceSetting::Global,
};
let opened =
self.remote_fs
.open(&self.remote_path, remote_options, self.remote_extra.clone())?;
let _ = self.remote.set(opened);
Ok(self.remote.get().expect("just set or already set"))
}
pub(super) fn local_state(&self) -> Result<&LocalState> {
if let Some(state) = self.local.get() {
return Ok(state);
}
self.init_local_state(true)?;
Ok(self.local.get().expect("just initialized"))
}
fn init_local_state(&self, allow_from_scratch: bool) -> Result<()> {
let mut guard = self.init_lock.lock();
if self.local.get().is_some() {
return Ok(());
}
let local = match std::mem::replace(&mut *guard, InitSource::FromScratch) {
InitSource::FromScratch => {
if !allow_from_scratch {
return Ok(());
}
self.new_local_state_from_scratch()?
}
InitSource::FromPrefiller(mut pipe) => {
match pipe.wait()? {
Some((blocks_range, bytes)) => {
let local = LocalState::new(
&self.local_path,
bytes.len() as u64,
self.open_options,
)?;
unsafe { local.write_mmap_bytes(&bytes, blocks_range) };
local
}
None => {
debug_assert!(
false,
"Looks like the request for prefill bytes was incorrect"
);
if !allow_from_scratch {
return Ok(());
}
self.new_local_state_from_scratch()?
}
}
}
};
self.local
.set(local)
.expect("OnceLock::set must succeed while holding init_lock");
Ok(())
}
fn new_local_state_from_scratch(&self) -> Result<LocalState> {
let len = self.remote()?.len::<u8>()?;
LocalState::new(&self.local_path, len, self.open_options)
}
fn populate_from(&self, byte_start: u64) -> std::result::Result<(), UniversalIoError> {
if crate::common::low_memory::low_memory_mode().skip_populate() {
return Ok(());
}
let remote_len = self.remote()?.len::<u8>()?;
if remote_len == 0 {
return Ok(());
}
let one_byte_per_block = (byte_start..remote_len)
.step_by(BLOCK_SIZE)
.map(|byte_offset| ((), ReadRange::one(byte_offset)));
for result in self.read_iter::<Sequential, u8, ()>(one_byte_per_block)? {
result?;
}
Ok(())
}
}
impl<R> UniversalRead for DiskCache<R>
where
R: UniversalRead + Clone,
R::Fs: Clone + Send + Sync,
<R::Fs as UniversalReadFs>::OpenExtra: Clone + Send + Sync,
R::OwnedReadPipeline<u8, Range<u32>>: Send,
{
type Fs = DiskCacheFs<R>;
type BorrowedReadPipeline<'a, T, U>
= DiskCachePipeline<'a, R, T, U>
where
R: 'a,
T: Item,
U: UserData;
type OwnedReadPipeline<T, U>
= OwnedDiskCachePipeline<R, T, U>
where
T: Item,
U: UserData;
fn reopen(&mut self) -> Result<()> {
self.init_local_state(false)?;
if self.local.get().is_none() {
return Ok(());
}
if let Some(remote) = self.remote.get_mut() {
remote.reopen()?;
}
let remote_len = self.remote()?.len::<u8>()?;
let local = self
.local
.get_mut()
.expect("We just ruled out `is_none` above, and we are holding &mut self");
let local_len = local.mmap().len::<u8>()?;
if local_len > remote_len {
return Err(UniversalIoError::Io(io::Error::new(
ErrorKind::UnexpectedEof,
format!(
"Reopen encountered a smaller file than expected; old_len: {local_len}, new_len: {remote_len}"
),
)));
}
if local_len == remote_len {
return Ok(());
}
local.resize(self.local_path.clone(), remote_len)?;
match self.open_options.populate {
Populate::Auto | Populate::No => {}
Populate::Blocking => self.populate_from(local_len)?,
Populate::PreferBackground => {
}
}
Ok(())
}
fn read<P, T>(&self, range: ReadRange) -> Result<Cow<'_, [T]>>
where
P: AccessPattern,
T: Item,
{
let (_, read) = self
.read_iter::<P, T, _>(std::iter::once(((), range)))?
.next()
.expect("there's exactly one read")?;
Ok(read)
}
fn len<T>(&self) -> Result<u64> {
let len = if let Some(local) = self.local.get() {
local.mmap().len::<T>()?
} else {
self.remote()?.len::<T>()?
};
Ok(len)
}
fn populate(&self) -> Result<()> {
self.populate_from(0)
}
fn clear_ram_cache(&self) -> Result<()> {
if let Some(state) = self.local.get() {
state.mmap().clear_ram_cache()?;
}
Ok(())
}
fn kind() -> UniversalKind {
UniversalKind::SimpleDiskCache
}
}