use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use std::future::Future;
use std::io;
use super::FileIo;
use crate::buf::owned::OwnedBuf;
use crate::buf::{LARGEST, PoolBuf, with_pool};
use crate::fs::call::{self, FileOutcome, SlotJob};
use crate::io::raw::Raw;
use crate::io::side::After;
use crate::io::sys;
use crate::rt::counters::tally;
use crate::rt::task::Task;
pub(super) const PAGE: usize = 4096;
pub(super) enum Work<T> {
Pool(Task<T>),
Slot(SlotJob<T>),
}
impl<T> Work<T>
where
T: Ended + call::End,
{
#[inline]
pub(super) fn poll(&mut self, waker: &Waker) -> Poll<T> {
let end = match self {
Work::Pool(task) => poll_task(task, waker),
Work::Slot(job) => job.poll(waker),
};
if let Poll::Ready(end) = &end
&& let Some(err) = end.error()
{
crate::rt::counters::io_error(err);
}
end
}
#[inline(always)]
pub(super) fn pooled(&self) -> bool {
matches!(self, Work::Pool(_))
}
}
#[cfg(feature = "io-stats")]
pub(super) type Counted = usize;
#[cfg(not(feature = "io-stats"))]
#[derive(Clone, Copy)]
pub(super) struct Counted;
#[inline(always)]
pub(super) fn counted(bytes: usize) -> Counted {
#[cfg(feature = "io-stats")]
return bytes;
#[cfg(not(feature = "io-stats"))]
{
let _ = bytes;
Counted
}
}
#[inline(always)]
pub(super) fn tally_write(bytes: Counted, ok: bool) {
#[cfg(feature = "io-stats")]
if bytes > 0 && ok && tally::counting() {
tally::add_write(bytes);
}
#[cfg(not(feature = "io-stats"))]
let _ = (bytes, ok);
}
#[inline(always)]
pub(super) fn tally_read<T>(pooled: bool, result: &io::Result<T>, bytes: fn(&T) -> usize) {
if pooled
&& tally::counting()
&& let Ok(read) = result
{
tally::add_read(bytes(read));
}
}
impl<T> Work<T> {
pub(super) fn let_go(self) {
match self {
Work::Pool(task) => task.detach(),
Work::Slot(job) => drop(job),
}
}
}
pub(super) 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>, OwnedBuf) {
fn error(&self) -> Option<&io::Error> {
self.0.as_ref().err()
}
}
pub(super) struct Job<T> {
pub(super) task: Work<io::Result<T>>,
pub(super) at: u64,
pub(super) epoch: u64,
pub(super) generation: u64,
}
pub(super) struct LentRead {
pub(super) task: Work<(io::Result<usize>, OwnedBuf)>,
pub(super) at: u64,
pub(super) epoch: u64,
pub(super) generation: u64,
pub(super) from: usize,
}
pub(super) enum Writing {
Pool(Work<io::Result<()>>, Counted),
Lent(Work<(io::Result<()>, OwnedBuf)>, Counted),
Append(Work<io::Result<u64>>, Counted),
AppendLent(Work<(io::Result<u64>, OwnedBuf)>, Counted),
}
#[derive(Default)]
pub(super) enum Lent {
#[default]
None,
Out,
Done(io::Result<()>, OwnedBuf),
Orphan,
}
pub(crate) fn pwrite_owned(fd: Raw, buf: &mut OwnedBuf, from: usize, at: u64) -> io::Result<()> {
let Some(bufs) = buf.vectored_mut() else {
return sys::pwrite_all(fd, buf.rest_from(from), at);
};
let (len, mut done) = (bufs.len(), from);
while done < len {
let n = sys::pwritev(fd, bufs.write_iovecs(done), at + (done - from) as u64)?;
if n == 0 {
return Err(io::ErrorKind::WriteZero.into());
}
done += n;
}
Ok(())
}
pub(crate) fn pread_owned(fd: Raw, buf: &mut OwnedBuf, at: u64) -> io::Result<usize> {
let result = match buf.vectored_mut() {
Some(bufs) => sys::preadv(fd, bufs.read_iovecs(), at),
None => sys::pread_uninit(fd, buf.spare_capacity_mut(), at),
};
if let Ok(n) = result {
unsafe { buf.add_len(n) };
}
result
}
fn append_owned(fd: Raw, buf: &mut OwnedBuf) -> io::Result<u64> {
let Some(bufs) = buf.vectored_mut() else {
return sys::append_all(fd, buf);
};
#[cfg(unix)]
{
let (len, mut done) = (bufs.len(), 0);
while done < len {
let n = sys::writev(fd, bufs.write_iovecs(done))?;
if n == 0 {
return Err(io::ErrorKind::WriteZero.into());
}
done += n;
}
sys::seek(fd, None)
}
#[cfg(windows)]
{
let (len, mut done, mut end) = (bufs.len(), 0, 0);
while done < len {
let rest = bufs.rest_from(done);
end = sys::append_all(fd, rest)?;
done += rest.len();
}
Ok(end)
}
}
#[inline]
pub(super) fn poll_task<T>(task: &mut Task<T>, waker: &Waker) -> Poll<T> {
Pin::new(task).poll(&mut Context::from_waker(waker))
}
#[inline]
pub(super) 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.set_starved();
}
buf
}
fn direct_bound(cap: usize) -> usize {
match cap / 4 {
0 => LARGEST,
quarter => (1usize << quarter.ilog2()).max(LARGEST),
}
}
fn first_part(len: usize, bound: usize) -> usize {
if len <= bound {
return len;
}
match len % bound {
0 => bound,
rest => rest,
}
}
pub(crate) fn bounce(
len: usize,
addr: usize,
direct: bool,
waker: &Waker,
) -> Option<(PoolBuf, usize, usize)> {
let shift = if direct { addr % PAGE } else { 0 };
with_pool(|pool| {
let pool = pool?;
let want = if direct {
first_part(len, direct_bound(pool.cap()))
} else {
len.min(LARGEST)
};
let size = if direct {
(want + shift).max(PAGE)
} else {
want
};
let buf = if pool.could_hold_whole(size) {
pool.get_whole(size)
} else {
pool.get(size)
};
let Some(buf) = buf else {
pool.wait_for_return(waker.clone());
return None;
};
let shift = if buf.capacity() > shift { shift } else { 0 };
let n = want.min(buf.capacity() - shift);
Some((buf, shift, n))
})
}
impl FileIo {
#[cfg(ring)]
#[inline]
pub(super) fn on_ring(&self) -> bool {
#[cfg(test)]
if self.on_pool.load(core::sync::atomic::Ordering::Relaxed) {
return false;
}
true
}
#[inline]
pub(super) fn submit_read(
&self,
buf: PoolBuf,
from: usize,
len: usize,
at: u64,
) -> Work<io::Result<PoolBuf>> {
#[cfg(ring)]
let buf = match self.on_ring() {
true => match crate::fs::ring::read(&self.fd, at, buf, from, len) {
Ok(job) => return Work::Slot(job),
Err(buf) => buf,
},
false => buf,
};
let fd = self.fd.clone();
let mut buf = buf;
Work::Pool(crate::runtime::spawn_blocking(move || {
let end = (from + len).min(buf.capacity());
let n = sys::pread(fd.raw(), &mut buf.spare()[from..end], at)?;
buf.set_filled_at(from, n);
Ok(buf)
}))
}
#[inline]
pub(super) fn submit_pwrite(
&self,
buf: PoolBuf,
at: u64,
waker: &Waker,
) -> (Work<io::Result<()>>, Counted) {
#[cfg(ring)]
let buf = match self.on_ring() {
true => match crate::fs::ring::write(&self.fd, at, buf) {
Ok(job) => return (Work::Slot(job), counted(0)),
Err(buf) => buf,
},
false => buf,
};
let fd = self.fd.clone();
let bytes = counted(buf.filled().len());
let work = Work::Slot(call::write_on_pool(
move || FileOutcome::Write(sys::pwrite_all(fd.raw(), buf.filled(), at)),
waker,
));
(work, bytes)
}
#[inline]
pub(super) fn submit_write(&self, buf: PoolBuf, at: u64, waker: &Waker) -> Writing {
if self.append {
let fd = self.fd.clone();
let bytes = counted(buf.filled().len());
let work = Work::Slot(call::write_on_pool(
move || FileOutcome::Append(sys::append_all(fd.raw(), buf.filled())),
waker,
));
return Writing::Append(work, bytes);
}
let (work, bytes) = self.submit_pwrite(buf, at, waker);
Writing::Pool(work, bytes)
}
#[inline]
pub(super) fn submit_read_owned(
&self,
held: &mut Option<OwnedBuf>,
at: u64,
waker: &Waker,
) -> Work<(io::Result<usize>, OwnedBuf)> {
#[cfg(ring)]
if self.on_ring()
&& let Some(job) = crate::fs::ring::read_lent(&self.fd, at, held, waker)
{
return Work::Slot(job);
}
#[cfg(not(ring))]
let _ = waker;
let fd = self.fd.clone();
let mut buf = match held.take().unwrap_or_default() {
OwnedBuf::Vec(mut vec) => {
return Work::Pool(crate::runtime::spawn_blocking(move || {
let result = sys::pread_uninit(fd.raw(), vec.spare_capacity_mut(), at);
if let Ok(n) = result {
unsafe { vec.set_len(vec.len() + n) };
}
(result, OwnedBuf::Vec(vec))
}));
}
other => other,
};
Work::Pool(crate::runtime::spawn_blocking(move || {
let result = pread_owned(fd.raw(), &mut buf, at);
(result, buf)
}))
}
#[inline]
pub(super) fn submit_pwrite_owned(
&self,
buf: OwnedBuf,
at: u64,
waker: &Waker,
) -> (Work<(io::Result<()>, OwnedBuf)>, Counted) {
#[cfg(ring)]
let buf = match self.on_ring() {
true => match crate::fs::ring::write_lent(&self.fd, at, buf) {
Ok(job) => return (Work::Slot(job), counted(0)),
Err(buf) => buf,
},
false => buf,
};
let fd = self.fd.clone();
let bytes = counted(buf.len());
let work = Work::Slot(call::write_on_pool(
move || {
let mut buf = buf;
let result = pwrite_owned(fd.raw(), &mut buf, 0, at);
FileOutcome::WriteLent(result, buf)
},
waker,
));
(work, bytes)
}
#[inline]
pub(super) fn submit_write_owned(&self, buf: OwnedBuf, at: u64, waker: &Waker) -> Writing {
if self.append {
let fd = self.fd.clone();
let bytes = counted(buf.len());
let work = Work::Slot(call::write_on_pool(
move || {
let mut buf = buf;
let result = append_owned(fd.raw(), &mut buf);
FileOutcome::AppendLent(result, buf)
},
waker,
));
return Writing::AppendLent(work, bytes);
}
let (work, bytes) = self.submit_pwrite_owned(buf, at, waker);
Writing::Lent(work, bytes)
}
}