use std::future::{Future, poll_fn};
use std::io::{self, SeekFrom};
use std::mem;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, ready};
use moirai_pal::fs::{File as Handle, FileOpenOptions};
use crate::blocking::{Abandoned, Admission, Completion};
use crate::fs::pool;
use crate::fs::stats::FileStats;
mod fence;
pub(in crate::fs) mod request;
mod traits;
use fence::Fence;
use request::{Outcome, Request};
type Admitting = Pin<Box<dyn Future<Output = io::Result<Admission>> + Send + Sync>>;
enum State {
Idle,
Admitting(Admitting),
Busy(Completion<Outcome>),
}
enum Settled {
Absorbed,
EndOfFile,
}
pub struct File {
handle: Arc<Handle>,
state: State,
fence: Arc<Fence>,
unread: Vec<u8>,
deferred: Option<io::Error>,
path: PathBuf,
stats: FileStats,
}
impl File {
pub async fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::open_with_options(path, FileOpenOptions::read_only()).await
}
pub async fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::open_with_options(path, FileOpenOptions::write_only()).await
}
pub async fn open_with_options<P: AsRef<Path>>(
path: P,
options: FileOpenOptions,
) -> io::Result<Self> {
let path = path.as_ref().to_path_buf();
let target = path.clone();
let handle = pool()
.run(Abandoned::Skip, move || Handle::open_with(target, options))
.await??;
Ok(Self {
handle: Arc::new(handle),
state: State::Idle,
fence: Arc::default(),
unread: Vec::new(),
deferred: None,
path,
stats: FileStats::default(),
})
}
pub async fn read_to_string(&mut self) -> io::Result<String> {
let contents = String::from_utf8(self.read_all().await?)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
Ok(contents)
}
pub async fn read_to_end(&mut self) -> io::Result<Vec<u8>> {
self.read_all().await
}
async fn read_all(&mut self) -> io::Result<Vec<u8>> {
let Outcome::Read(contents) = self
.perform(Request::ReadToEnd { prefix: Vec::new() })
.await?
else {
unreachable!("invariant: a read-to-end request yields a read outcome");
};
let contents = contents?;
self.stats.bytes_read += contents.len() as u64;
self.stats.read_operations += 1;
Ok(contents)
}
pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bytes_read = poll_fn(|cx| self.poll_read_into(cx, buf)).await?;
self.stats.bytes_read += bytes_read as u64;
self.stats.read_operations += 1;
Ok(bytes_read)
}
pub async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let Outcome::Wrote(written) = self
.perform(Request::Write {
data: buf.to_vec(),
rewind: 0,
})
.await?
else {
unreachable!("invariant: a write request yields a write outcome");
};
let bytes_written = written?;
self.stats.bytes_written += bytes_written as u64;
self.stats.write_operations += 1;
Ok(bytes_written)
}
pub async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
if buf.is_empty() {
return Ok(());
}
let Outcome::Done(done) = self
.perform(Request::WriteAll {
data: buf.to_vec(),
rewind: 0,
})
.await?
else {
unreachable!("invariant: a write-all request yields a done outcome");
};
done?;
self.stats.bytes_written += buf.len() as u64;
self.stats.write_operations += 1;
Ok(())
}
pub async fn write_str(&mut self, s: &str) -> io::Result<()> {
self.write_all(s.as_bytes()).await
}
pub async fn flush(&mut self) -> io::Result<()> {
poll_fn(|cx| self.poll_settled(cx)).await
}
pub async fn sync_all(&mut self) -> io::Result<()> {
self.perform_unit(Request::SyncAll).await
}
pub async fn sync_data(&mut self) -> io::Result<()> {
self.perform_unit(Request::SyncData).await
}
async fn perform_unit(&mut self, request: Request) -> io::Result<()> {
let Outcome::Done(done) = self.perform(request).await? else {
unreachable!("invariant: a sync request yields a done outcome");
};
done
}
pub async fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let Outcome::Sought(position) = self.perform(Request::Seek(pos)).await? else {
unreachable!("invariant: a seek request yields a seek outcome");
};
let new_pos = position?;
self.stats.seek_operations += 1;
Ok(new_pos)
}
pub async fn stream_position(&mut self) -> io::Result<u64> {
let Outcome::Sought(position) = self.perform(Request::Seek(SeekFrom::Current(0))).await?
else {
unreachable!("invariant: a seek request yields a seek outcome");
};
position
}
pub async fn metadata(&self) -> io::Result<std::fs::Metadata> {
self.fence.settled().await;
let handle = Arc::clone(&self.handle);
pool()
.run(Abandoned::Skip, move || handle.metadata())
.await?
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn stats(&self) -> &FileStats {
&self.stats
}
async fn perform(&mut self, request: Request) -> io::Result<Outcome> {
let mut request = Some(request);
poll_fn(|cx| {
if request.is_some() {
ready!(self.poll_settle(cx));
if let Some(error) = self.deferred.take() {
request = None;
return Poll::Ready(Err(error));
}
let admission = ready!(self.poll_admission(cx))?;
let pending = request
.take()
.expect("invariant: the request is submitted at most once, checked above");
self.submit(admission, pending)?;
}
let State::Busy(completion) = &mut self.state else {
unreachable!(
"invariant: this call's request is in flight while it borrows the file"
);
};
let outcome = ready!(Pin::new(completion).poll(cx));
self.state = State::Idle;
Poll::Ready(outcome)
})
.await
}
fn submit(&mut self, admission: Admission, request: Request) -> io::Result<()> {
let request = match request {
Request::ReadToEnd { .. } => Request::ReadToEnd {
prefix: mem::take(&mut self.unread),
},
Request::Write { data, .. } => Request::Write {
data,
rewind: self.take_unread_len(),
},
Request::WriteAll { data, .. } => Request::WriteAll {
data,
rewind: self.take_unread_len(),
},
Request::Seek(SeekFrom::Current(offset)) => {
let rewind = i64::try_from(self.unread.len()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"unread bytes exceed a seek offset",
)
})?;
let offset = offset.checked_sub(rewind).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "seek offset overflows i64")
})?;
self.unread.clear();
Request::Seek(SeekFrom::Current(offset))
}
Request::Seek(absolute) => {
self.unread.clear();
Request::Seek(absolute)
}
other => other,
};
let handle = Arc::clone(&self.handle);
let abandoned = request.abandoned();
let ticket = self.fence.issue();
let completion = admission.submit(abandoned, move || {
let outcome = request.run(&handle);
#[cfg(test)]
request::test_hooks::after_run();
drop(ticket);
outcome
})?;
self.state = State::Busy(completion);
Ok(())
}
fn take_unread_len(&mut self) -> u64 {
let rewind = self.unread.len() as u64;
self.unread.clear();
rewind
}
fn poll_settle(&mut self, cx: &mut Context<'_>) -> Poll<Settled> {
let State::Busy(completion) = &mut self.state else {
return Poll::Ready(Settled::Absorbed);
};
let outcome = ready!(Pin::new(completion).poll(cx));
self.state = State::Idle;
Poll::Ready(match outcome {
Ok(Outcome::Read(Ok(data))) if data.is_empty() => Settled::EndOfFile,
Ok(Outcome::Read(Ok(data))) => {
self.unread.extend_from_slice(&data);
Settled::Absorbed
}
Ok(outcome) => {
if let Some(error) = outcome.into_error() {
self.defer(error);
}
Settled::Absorbed
}
Err(error) => {
self.defer(error);
Settled::Absorbed
}
})
}
fn poll_settled(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
ready!(self.poll_settle(cx));
Poll::Ready(self.deferred.take().map_or(Ok(()), Err))
}
fn defer(&mut self, error: io::Error) {
if self.deferred.is_none() {
self.deferred = Some(error);
}
}
fn poll_admission(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Admission>> {
if matches!(self.state, State::Idle) {
self.state = State::Admitting(Box::pin(pool().admit()));
}
let State::Admitting(admitting) = &mut self.state else {
unreachable!("invariant: callers settle a busy state before admission");
};
let admitted = ready!(admitting.as_mut().poll(cx));
self.state = State::Idle;
Poll::Ready(admitted)
}
fn poll_read_into(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<io::Result<usize>> {
loop {
if !self.unread.is_empty() {
let delivered = self.unread.len().min(buf.len());
buf[..delivered].copy_from_slice(&self.unread[..delivered]);
self.unread.drain(..delivered);
return Poll::Ready(Ok(delivered));
}
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
if matches!(self.state, State::Busy(_)) {
if let Settled::EndOfFile = ready!(self.poll_settle(cx)) {
return Poll::Ready(Ok(0));
}
continue;
}
if let Some(error) = self.deferred.take() {
return Poll::Ready(Err(error));
}
let admission = ready!(self.poll_admission(cx))?;
self.submit(admission, Request::Read { len: buf.len() })?;
}
}
fn poll_write_behind(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
ready!(self.poll_settled(cx))?;
let admission = ready!(self.poll_admission(cx))?;
self.submit(
admission,
Request::WriteAll {
data: buf.to_vec(),
rewind: 0,
},
)?;
Poll::Ready(Ok(buf.len()))
}
}