use core::future::poll_fn;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use std::future::Future;
use std::io::{self, SeekFrom};
use std::sync::Arc;
use std::task::Wake;
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use crate::buf::{PoolBuf, with_pool};
use crate::io::raw::{OwnedFile, Raw, file_raw};
use crate::io::side::{After, Hooks, Side, WakerSet};
use crate::io::source::Failure;
use crate::io::sys;
use crate::rt::task::Task;
pub(crate) const READ_MIN: usize = 64 * 1024;
const WRITE_MIN: usize = 64 * 1024;
const FILE_SLOTS: usize = 8;
mod work {
pub(super) const CONSUME: u8 = 1;
pub(super) const ABANDON: u8 = 2;
}
pub(crate) struct FileIo {
fd: Arc<OwnedFile>,
state: Side<FileState>,
waiters: WakerSet<FILE_SLOTS>,
consumed: AtomicUsize,
unsent: AtomicBool,
cursor: AtomicU64,
append: bool,
#[cfg(test)]
on_pool: AtomicBool,
}
struct FileHooks<'a> {
io: &'a Arc<FileIo>,
}
impl Hooks<FileState> for FileHooks<'_> {
fn serve(&self, _st: &mut FileState, _after: &mut After) {}
fn deferred(&self, st: &mut FileState, bits: u8, after: &mut After) {
if bits & work::CONSUME != 0 {
let amt = self.io.consumed.swap(0, Ordering::Acquire);
let n = st.ahead.as_mut().map_or(0, |ahead| {
let n = amt.min(ahead.filled().len());
ahead.consume(n);
n
});
st.consumed(n, after);
}
if bits & work::ABANDON != 0 {
st.abandon_lent(&Waker::from(Arc::clone(self.io)), after);
}
}
fn releasing(&self, st: &mut FileState) {
self.io.unsent.store(
st.writing.is_some() || st.behind.is_some(),
Ordering::Release,
);
self.io.cursor.store(st.cursor, Ordering::Release);
}
}
enum Work<T> {
Pool(Task<T>),
#[cfg(ring)]
Ring(super::ring::RingJob<T>),
}
impl<T: Ended> Work<T> {
fn poll(&mut self, waker: &Waker) -> Poll<T> {
let end = match self {
Work::Pool(task) => poll_task(task, waker),
#[cfg(ring)]
Work::Ring(job) => job.poll(waker),
};
if let Poll::Ready(end) = &end
&& let Some(err) = end.error()
{
crate::rt::counters::io_error(err);
}
end
}
}
trait Ended {
fn error(&self) -> Option<&io::Error>;
}
impl<T> Ended for io::Result<T> {
fn error(&self) -> Option<&io::Error> {
self.as_ref().err()
}
}
impl<T> Ended for (io::Result<T>, Vec<u8>) {
fn error(&self) -> Option<&io::Error> {
self.0.as_ref().err()
}
}
struct Job<T> {
task: Work<io::Result<T>>,
at: u64,
epoch: u64,
}
struct LentRead {
task: Work<(io::Result<usize>, Vec<u8>)>,
at: u64,
epoch: u64,
from: usize,
}
enum Writing {
Pool(Work<io::Result<()>>),
Lent(Work<(io::Result<()>, Vec<u8>)>),
Append(Work<io::Result<u64>>),
AppendLent(Work<(io::Result<u64>, Vec<u8>)>),
}
#[derive(Default)]
enum Lent {
#[default]
None,
Out,
Done(io::Result<()>, Vec<u8>),
Orphan,
}
#[derive(Default)]
struct FileState {
cursor: u64,
epoch: u64,
ahead: Option<PoolBuf>,
reading: Option<Job<PoolBuf>>,
writing: Option<Writing>,
lent: Lent,
behind: Option<PoolBuf>,
behind_at: u64,
failed: Option<Failure>,
sizing: Option<Task<io::Result<u64>>>,
}
fn poll_task<T>(task: &mut Task<T>, waker: &Waker) -> Poll<T> {
Pin::new(task).poll(&mut Context::from_waker(waker))
}
fn buffer(len: usize, after: &mut After) -> Option<PoolBuf> {
let buf = with_pool(|pool| pool.and_then(|p| p.get(len)));
if buf.is_none() {
after.starved = true;
}
buf
}
impl FileIo {
pub(crate) fn new(fd: Raw) -> io::Result<Arc<Self>> {
let cursor = sys::seek(fd, None)?;
let append = sys::appends(fd)?;
Ok(Arc::new(Self {
fd: Arc::new(sys::duplicate_file(fd)?),
state: Side::new(FileState {
cursor,
..FileState::default()
}),
waiters: WakerSet::new(),
consumed: AtomicUsize::new(0),
unsent: AtomicBool::new(false),
cursor: AtomicU64::new(cursor),
append,
#[cfg(test)]
on_pool: AtomicBool::new(false),
}))
}
#[cfg(test)]
pub(crate) fn keep_on_pool(&self) {
self.on_pool.store(true, Ordering::Relaxed);
}
#[cfg(ring)]
fn on_ring(&self) -> bool {
#[cfg(test)]
if self.on_pool.load(Ordering::Relaxed) {
return false;
}
true
}
fn submit_read(&self, buf: PoolBuf, at: u64, epoch: u64) -> Job<PoolBuf> {
#[cfg(ring)]
let buf = match self.on_ring() {
true => match super::ring::read(&self.fd, at, buf) {
Ok(job) => {
return Job {
task: Work::Ring(job),
at,
epoch,
};
}
Err(buf) => buf,
},
false => buf,
};
let fd = Arc::clone(&self.fd);
let mut buf = buf;
let task = crate::runtime::spawn_blocking(move || {
let n = sys::pread(file_raw(&fd), buf.spare(), at)?;
buf.set_filled(n);
Ok(buf)
});
Job {
task: Work::Pool(task),
at,
epoch,
}
}
fn submit_write(&self, buf: PoolBuf, at: u64) -> Writing {
if self.append {
let fd = Arc::clone(&self.fd);
return Writing::Append(Work::Pool(crate::runtime::spawn_blocking(move || {
sys::append_all(file_raw(&fd), buf.filled())
})));
}
#[cfg(ring)]
let buf = match self.on_ring() {
true => match super::ring::write(&self.fd, at, buf) {
Ok(job) => return Writing::Pool(Work::Ring(job)),
Err(buf) => buf,
},
false => buf,
};
let fd = Arc::clone(&self.fd);
Writing::Pool(Work::Pool(crate::runtime::spawn_blocking(move || {
sys::pwrite_all(file_raw(&fd), buf.filled(), at)
})))
}
fn submit_read_owned(&self, buf: Vec<u8>, at: u64, epoch: u64) -> LentRead {
let from = buf.len();
#[cfg(ring)]
let buf = match self.on_ring() {
true => match super::ring::read_lent(&self.fd, at, buf) {
Ok(job) => {
return LentRead {
task: Work::Ring(job),
at,
epoch,
from,
};
}
Err(buf) => buf,
},
false => buf,
};
let fd = Arc::clone(&self.fd);
let mut buf = buf;
let task = crate::runtime::spawn_blocking(move || {
let result = sys::pread_uninit(file_raw(&fd), buf.spare_capacity_mut(), at);
if let Ok(n) = result {
unsafe { buf.set_len(buf.len() + n) };
}
(result, buf)
});
LentRead {
task: Work::Pool(task),
at,
epoch,
from,
}
}
fn submit_write_owned(&self, buf: Vec<u8>, at: u64) -> Writing {
if self.append {
let fd = Arc::clone(&self.fd);
return Writing::AppendLent(Work::Pool(crate::runtime::spawn_blocking(move || {
(sys::append_all(file_raw(&fd), &buf), buf)
})));
}
#[cfg(ring)]
let buf = match self.on_ring() {
true => match super::ring::write_lent(&self.fd, at, buf) {
Ok(job) => return Writing::Lent(Work::Ring(job)),
Err(buf) => buf,
},
false => buf,
};
let fd = Arc::clone(&self.fd);
Writing::Lent(Work::Pool(crate::runtime::spawn_blocking(move || {
(sys::pwrite_all(file_raw(&fd), &buf, at), buf)
})))
}
fn with_state<R>(
self: &Arc<Self>,
cx: &Context<'_>,
mut step: impl FnMut(&mut FileState, &FileIo, &Waker, &mut After) -> Poll<R>,
) -> Poll<R> {
self.waiters.register(cx.waker());
let waker = Waker::from(Arc::clone(self));
let mut after = After::default();
let hooks = FileHooks { io: self };
let out = self
.state
.poll(&mut after, cx.waker(), &hooks, |st, after| {
step(st, self, &waker, after)
});
if after.starved {
with_pool(|pool| {
if let Some(pool) = pool {
pool.wait_for_return(waker.clone());
}
});
}
after.run();
out
}
pub(crate) fn poll_read(
self: &Arc<Self>,
cx: &mut Context<'_>,
out: &mut [u8],
) -> Poll<io::Result<usize>> {
if out.is_empty() {
return Poll::Ready(Ok(0));
}
crate::coop::poll_budget(cx, |cx| {
self.with_state(cx, |st, io, waker, after| {
match st.fill(io, out.len(), waker, after) {
Poll::Ready(Ok(true)) => {}
other => return other.map_ok(|_| 0),
}
let n = st.ahead.as_mut().map_or(0, |ahead| ahead.read_into(out));
st.consumed(n, after);
Poll::Ready(Ok(n))
})
})
}
pub(crate) fn poll_fill_buf(
self: &Arc<Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<(*const u8, usize)>> {
self.with_state(cx, |st, io, waker, after| {
st.fill(io, READ_MIN, waker, after).map_ok(|filled| {
match st.ahead.as_ref().filter(|_| filled) {
Some(ahead) => (ahead.filled().as_ptr(), ahead.filled().len()),
None => (
core::ptr::NonNull::<u8>::dangling().as_ptr().cast_const(),
0,
),
}
})
})
}
pub(crate) fn consume(self: &Arc<Self>, amt: usize) {
self.consumed.fetch_add(amt, Ordering::Release);
let mut after = After::default();
self.state
.defer(work::CONSUME, &mut after, &FileHooks { io: self });
after.run();
}
pub(crate) fn poll_read_vectored(
self: &Arc<Self>,
cx: &mut Context<'_>,
bufs: &mut [io::IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
let want: usize = bufs.iter().map(|b| b.len()).sum();
if want == 0 {
return Poll::Ready(Ok(0));
}
crate::coop::poll_budget(cx, |cx| self.poll_read_vectored_unbudgeted(cx, bufs, want))
}
fn poll_read_vectored_unbudgeted(
self: &Arc<Self>,
cx: &mut Context<'_>,
bufs: &mut [io::IoSliceMut<'_>],
want: usize,
) -> Poll<io::Result<usize>> {
self.with_state(cx, |st, io, waker, after| {
match st.fill(io, want, waker, after) {
Poll::Ready(Ok(true)) => {}
other => return other.map_ok(|_| 0),
}
let n = st
.ahead
.as_mut()
.map_or(0, |ahead| ahead.read_into_vectored(bufs));
st.consumed(n, after);
Poll::Ready(Ok(n))
})
}
pub(crate) async fn read_owned(self: &Arc<Self>, buf: Vec<u8>) -> (io::Result<usize>, Vec<u8>) {
let mut held = Some(buf);
let mut job = None;
let result = poll_fn(|cx| self.poll_read_owned(cx, &mut held, &mut job)).await;
(result, held.unwrap_or_default())
}
fn poll_read_owned(
self: &Arc<Self>,
cx: &mut Context<'_>,
held: &mut Option<Vec<u8>>,
job: &mut Option<LentRead>,
) -> Poll<io::Result<usize>> {
if held.as_ref().is_some_and(|b| b.len() == b.capacity()) {
return Poll::Ready(Ok(0));
}
self.with_state(cx, |st, io, waker, after| {
loop {
if let Some(lent) = job.as_mut() {
let (result, mut buf) = match lent.task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(out) => out,
};
let (at, epoch, from) = (lent.at, lent.epoch, lent.from);
*job = None;
if at != st.cursor || epoch != st.epoch {
buf.truncate(from);
*held = Some(buf);
continue;
}
*held = Some(buf);
if let Ok(n) = result {
st.cursor += n as u64;
if let Some(ahead) = st.ahead.take() {
after.drop_buf(ahead);
}
}
return Poll::Ready(result);
}
if st.poll_writes(io, waker, after).is_pending() {
return Poll::Pending;
}
let Some(buf) = held.as_mut() else {
return Poll::Ready(Err(io::Error::other("the read lost its buffer")));
};
if let Some(ahead) = st.ahead.as_mut().filter(|a| !a.is_empty()) {
let n = crate::buf::append_spare(ahead.filled(), buf);
ahead.consume(n);
st.consumed(n, after);
return Poll::Ready(Ok(n));
}
if let Some(buf) = held.take() {
*job = Some(io.submit_read_owned(buf, st.cursor, st.epoch));
}
}
})
}
pub(crate) async fn write_owned(
self: &Arc<Self>,
buf: Vec<u8>,
) -> (io::Result<usize>, Vec<u8>) {
let mut op = WriteOwned {
io: self,
held: Some(buf),
out: false,
};
let result = poll_fn(|cx| op.poll(cx)).await;
(result, op.held.take().unwrap_or_default())
}
pub(crate) fn poll_write(
self: &Arc<Self>,
cx: &mut Context<'_>,
bytes: &[u8],
) -> Poll<io::Result<usize>> {
self.poll_write_vectored(cx, &[io::IoSlice::new(bytes)])
}
pub(crate) fn poll_write_vectored(
self: &Arc<Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
crate::coop::poll_budget(cx, |cx| {
self.with_state(cx, |st, io, waker, after| st.write(io, bufs, waker, after))
})
}
pub(crate) fn poll_flush(self: &Arc<Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.with_state(cx, |st, io, waker, after| {
match st.poll_writes(io, waker, after) {
Poll::Pending => Poll::Pending,
Poll::Ready(()) => Poll::Ready(st.failed.take().map_or(Ok(()), |f| Err(f.error()))),
}
})
}
pub(crate) fn poll_seek(
self: &Arc<Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
self.with_state(cx, |st, io, waker, after| st.seek(io, pos, waker, after))
}
pub(crate) fn has_unsent(&self) -> bool {
self.unsent.load(Ordering::Acquire)
}
pub(crate) fn cursor(&self) -> u64 {
self.cursor.load(Ordering::Acquire)
}
}
impl Wake for FileIo {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
let mut after = After::default();
self.waiters.take_all(&mut after);
after.run();
}
}
impl FileState {
fn invalidate(&mut self, after: &mut After) {
if let Some(ahead) = self.ahead.take() {
after.drop_buf(ahead);
}
self.epoch += 1;
}
fn consumed(&mut self, n: usize, after: &mut After) {
self.cursor += n as u64;
if self.ahead.as_ref().is_some_and(PoolBuf::is_empty)
&& let Some(done) = self.ahead.take()
{
after.drop_buf(done);
}
}
fn poll_writes(&mut self, io: &FileIo, waker: &Waker, after: &mut After) -> Poll<()> {
loop {
if let Some(writing) = self.writing.as_mut() {
let (failure, end) = match writing {
Writing::Pool(task) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => (result.as_ref().err().map(Failure::of), None),
},
Writing::Append(task) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => {
(result.as_ref().err().map(Failure::of), result.ok())
}
},
Writing::Lent(task) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready((result, buf)) => (self.lent_back(result, buf), None),
},
Writing::AppendLent(task) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready((result, buf)) => {
let end = result.as_ref().ok().copied();
(self.lent_back(result.map(drop), buf), end)
}
},
};
self.writing = None;
if let Some(end) = end {
self.cursor = end;
}
if let Some(failure) = failure {
self.failed = Some(failure);
if let Some(behind) = self.behind.take() {
after.drop_buf(behind);
}
}
}
match self.behind.take() {
Some(behind) => self.writing = Some(io.submit_write(behind, self.behind_at)),
None => return Poll::Ready(()),
}
}
}
fn lent_back(&mut self, result: io::Result<()>, buf: Vec<u8>) -> Option<Failure> {
let failure = result.as_ref().err().map(Failure::of);
if matches!(self.lent, Lent::Out) {
self.lent = Lent::Done(result, buf);
} else {
self.lent = Lent::None;
}
failure
}
fn fill(
&mut self,
io: &FileIo,
want: usize,
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<bool>> {
if self.poll_writes(io, waker, after).is_pending() {
return Poll::Pending;
}
loop {
if self.ahead.as_ref().is_some_and(|ahead| !ahead.is_empty()) {
return Poll::Ready(Ok(true));
}
if let Some(job) = self.reading.as_mut() {
let result = match job.task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => result,
};
let (at, epoch) = (job.at, job.epoch);
self.reading = None;
match result {
Err(err) => return Poll::Ready(Err(err)),
Ok(buf) if at != self.cursor || epoch != self.epoch => after.drop_buf(buf),
Ok(buf) if buf.is_empty() => {
after.drop_buf(buf);
return Poll::Ready(Ok(false));
}
Ok(buf) => {
if let Some(old) = self.ahead.replace(buf) {
after.drop_buf(old);
}
}
}
continue;
}
let Some(buf) = buffer(want.max(READ_MIN), after) else {
return Poll::Pending;
};
self.reading = Some(io.submit_read(buf, self.cursor, self.epoch));
}
}
fn write_owned(
&mut self,
io: &FileIo,
held: &mut Option<Vec<u8>>,
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<usize>> {
if let Some(buf) = held.as_ref() {
if !matches!(self.lent, Lent::None) {
let _ = self.poll_writes(io, waker, after);
return Poll::Pending;
}
if self.poll_writes(io, waker, after).is_pending() {
return Poll::Pending;
}
if let Some(failure) = self.failed {
return Poll::Ready(Err(failure.error()));
}
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let Some(buf) = held.take() else {
return Poll::Pending;
};
let len = buf.len() as u64;
self.invalidate(after);
self.writing = Some(io.submit_write_owned(buf, self.cursor));
self.cursor += len;
self.lent = Lent::Out;
}
let _ = self.poll_writes(io, waker, after);
match core::mem::take(&mut self.lent) {
Lent::Done(result, buf) => {
let len = buf.len();
*held = Some(buf);
after.wake_one(waker.clone());
Poll::Ready(result.map(|()| len))
}
other => {
self.lent = other;
Poll::Pending
}
}
}
fn abandon_lent(&mut self, waker: &Waker, after: &mut After) {
match core::mem::take(&mut self.lent) {
Lent::Out => self.lent = Lent::Orphan,
Lent::Done(Err(err), _) => self.failed = Some(Failure::of(&err)),
_ => {}
}
after.wake_one(waker.clone());
}
fn write(
&mut self,
io: &FileIo,
bufs: &[io::IoSlice<'_>],
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<usize>> {
let _ = self.poll_writes(io, waker, after);
if let Some(failure) = self.failed {
return Poll::Ready(Err(failure.error()));
}
let len: usize = bufs.iter().map(|b| b.len()).sum();
if len == 0 {
return Poll::Ready(Ok(0));
}
self.invalidate(after);
let n = if self.writing.is_none() {
let Some(mut buf) = buffer(len.max(WRITE_MIN), after) else {
return Poll::Pending;
};
let n = buf.append_vectored(bufs);
self.writing = Some(io.submit_write(buf, self.cursor));
n
} else if let Some(behind) = self.behind.as_mut() {
match behind.append_vectored(bufs) {
0 => return Poll::Pending,
n => n,
}
} else {
let Some(mut buf) = buffer(len.max(WRITE_MIN), after) else {
return Poll::Pending;
};
let n = buf.append_vectored(bufs);
self.behind = Some(buf);
self.behind_at = self.cursor;
n
};
self.cursor += n as u64;
Poll::Ready(Ok(n))
}
fn seek(
&mut self,
io: &FileIo,
pos: SeekFrom,
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<u64>> {
if self.poll_writes(io, waker, after).is_pending() {
return Poll::Pending;
}
let target = match pos {
SeekFrom::Start(n) => Some(n),
SeekFrom::Current(d) => self.cursor.checked_add_signed(d),
SeekFrom::End(d) => {
let task = self.sizing.get_or_insert_with(|| {
let fd = Arc::clone(&io.fd);
crate::runtime::spawn_blocking(move || sys::file_size(file_raw(&fd)))
});
let size = match poll_task(task, waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(size) => size,
};
self.sizing = None;
match size {
Ok(size) => size.checked_add_signed(d),
Err(err) => {
crate::rt::counters::io_error(&err);
return Poll::Ready(Err(err));
}
}
}
};
let Some(target) = target else {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the seek goes before the start of the file",
)));
};
if target != self.cursor {
self.invalidate(after);
self.cursor = target;
}
Poll::Ready(Ok(target))
}
}
struct WriteOwned<'a> {
io: &'a Arc<FileIo>,
held: Option<Vec<u8>>,
out: bool,
}
impl WriteOwned<'_> {
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
let held = &mut self.held;
let poll = self.io.with_state(cx, |st, io, waker, after| {
st.write_owned(io, held, waker, after)
});
self.out = self.held.is_none();
poll
}
}
impl Drop for WriteOwned<'_> {
fn drop(&mut self) {
if self.out {
let mut after = After::default();
self.io
.state
.defer(work::ABANDON, &mut after, &FileHooks { io: self.io });
after.run();
}
}
}