#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
mod cached;
mod copy;
mod durable;
mod many;
mod orphans;
mod owned;
mod positional;
mod space;
mod work;
use crate::buf::owned::OwnedBuf;
use core::task::{Context, Poll, Waker};
use std::io::{self, SeekFrom};
use std::sync::{Arc, OnceLock};
use std::task::Wake;
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use self::orphans::{Orphan, Orphans};
use self::work::{Job, Lent, Writing, bounce, buffer, poll_task, tally_read, tally_write};
use crate::buf::{PoolBuf, with_pool};
use crate::io::side::{After, Hooks, Side, WakerSet};
use crate::io::source::Failure;
use crate::io::source::requests::Lease;
use crate::io::sys;
pub(crate) use self::copy::COPY_CHUNK;
#[cfg(ring)]
pub(crate) use self::durable::sync_on;
pub(crate) use self::durable::{SyncKind, sync_file};
#[cfg(test)]
pub(crate) use self::many::MANY_AT_ONCE;
pub(crate) use self::space::range_end;
#[cfg(all(test, target_os = "linux"))]
pub(crate) use self::work::bounce as bounce_for_test;
#[cfg(ring)]
pub(crate) use self::work::{pread_owned, pwrite_owned};
pub(crate) const READ_MIN: usize = 64 * 1024;
const WRITE_MIN: usize = 64 * 1024;
const FILE_SLOTS: usize = 8;
mod deferred {
pub(super) const CONSUME: u8 = 1;
pub(super) const ABANDON: u8 = 2;
pub(super) const ORPHAN: u8 = 4;
}
pub(crate) struct FileIo {
fd: Lease,
state: Side<FileState>,
waiters: WakerSet<FILE_SLOTS>,
consumed: AtomicUsize,
unsent: AtomicBool,
cursor: AtomicU64,
generation: AtomicU64,
orphans: Orphans,
sync_failed: OnceLock<Failure>,
append: bool,
direct: 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 & deferred::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 & deferred::ABANDON != 0 {
st.abandon_lent(&Waker::from(Arc::clone(self.io)), after);
}
if bits & deferred::ORPHAN != 0 {
self.io.orphans.take(&mut st.orphans);
}
}
fn releasing(&self, st: &mut FileState) {
self.io.unsent.store(
st.writing.is_some() || st.behind.is_some() || !st.orphans.is_empty(),
Ordering::Release,
);
self.io.cursor.store(st.cursor, Ordering::Release);
}
fn releases_inline(&self) -> bool {
true
}
}
#[derive(Default)]
struct FileState {
cursor: u64,
epoch: u64,
ahead: Option<PoolBuf>,
ahead_gen: u64,
reading: Option<Job<PoolBuf>>,
writing: Option<Writing>,
lent: Lent,
behind: Option<PoolBuf>,
behind_at: u64,
failed: Option<Failure>,
sizing: Option<crate::rt::task::Task<io::Result<u64>>>,
orphans: Vec<Orphan>,
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
cache: cached::Cache,
}
impl FileIo {
pub(crate) fn new(fd: Lease) -> io::Result<Arc<Self>> {
let cursor = sys::seek(fd.raw(), None)?;
let mode = sys::file_mode(fd.raw())?;
Ok(Arc::new(Self {
fd,
state: Side::new(FileState {
cursor,
..FileState::default()
}),
waiters: WakerSet::new(),
consumed: AtomicUsize::new(0),
unsent: AtomicBool::new(false),
cursor: AtomicU64::new(cursor),
generation: AtomicU64::new(0),
orphans: Orphans::new(),
sync_failed: OnceLock::new(),
append: mode.append,
direct: mode.direct,
#[cfg(test)]
on_pool: AtomicBool::new(false),
}))
}
#[cfg(test)]
pub(crate) fn keep_on_pool(&self) {
self.on_pool.store(true, Ordering::Relaxed);
}
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
#[inline(always)]
fn reads_cached(&self) -> bool {
#[cfg(test)]
if self.on_pool.load(Ordering::Relaxed) {
return false;
}
!self.direct
}
#[cfg(all(test, target_os = "linux", target_pointer_width = "64"))]
pub(crate) fn tries_cache(self: &Arc<Self>) -> bool {
let mut after = After::default();
let hooks = FileHooks { io: self };
let held = self.state.try_hold().map(|mut held| {
let cache = held.get().cache;
held.release(&mut after, &hooks);
cache
});
after.run();
cached::has_call() && held.is_some_and(|cache| cache != cached::Cache::Never)
}
#[cfg(test)]
pub(crate) fn back_to_ring(&self) {
self.on_pool.store(false, Ordering::Relaxed);
}
#[cfg(all(test, target_os = "linux"))]
pub(crate) fn jobs_fd(&self) -> std::os::fd::RawFd {
self.fd.raw()
}
fn generation(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
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(), out.as_ptr() as usize, 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, 0, 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(deferred::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>> {
if self.direct {
return match bufs.iter_mut().find(|b| !b.is_empty()) {
Some(buf) => self.poll_read(cx, buf),
None => Poll::Ready(Ok(0)),
};
}
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, 0, 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) 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 counts(&self, io: &FileIo, at: u64, epoch: u64, generation: u64) -> bool {
at == self.cursor && epoch == self.epoch && generation == io.generation()
}
fn drop_stale_ahead(&mut self, io: &FileIo, after: &mut After) {
if self.ahead.is_some()
&& self.ahead_gen != io.generation()
&& let Some(stale) = self.ahead.take()
{
after.drop_buf(stale);
}
}
#[inline(always)]
fn writes_idle(&self) -> bool {
self.writing.is_none() && self.behind.is_none() && self.orphans.is_empty()
}
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, bytes) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => {
tally_write(*bytes, result.is_ok());
(result.as_ref().err().map(Failure::of), None)
}
},
Writing::Append(task, bytes) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => {
tally_write(*bytes, result.is_ok());
(result.as_ref().err().map(Failure::of), result.ok())
}
},
Writing::Lent(task, bytes) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready((result, buf)) => {
tally_write(*bytes, result.is_ok());
(self.lent_back(result, buf), None)
}
},
Writing::AppendLent(task, bytes) => match task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready((result, buf)) => {
tally_write(*bytes, result.is_ok());
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);
}
}
}
if self.poll_orphans(io, waker).is_pending() {
return Poll::Pending;
}
match self.behind.take() {
Some(behind) => self.writing = Some(io.submit_write(behind, self.behind_at, waker)),
None => return Poll::Ready(()),
}
}
}
#[inline]
fn poll_orphans(&mut self, io: &FileIo, waker: &Waker) -> Poll<()> {
if self.orphans.is_empty() {
return Poll::Ready(());
}
self.poll_left(io, waker)
}
#[cold]
#[inline(never)]
fn poll_left(&mut self, io: &FileIo, waker: &Waker) -> Poll<()> {
io.orphans.take(&mut self.orphans);
if self.orphans.is_empty() {
return Poll::Ready(());
}
let before = self.orphans.len();
let failed = &mut self.failed;
self.orphans.retain_mut(|orphan| match orphan.poll(waker) {
Poll::Pending => true,
Poll::Ready(err) => {
if let Some(err) = err {
*failed = Some(Failure::of(&err));
}
false
}
});
let landed = before - self.orphans.len();
if landed > 0 {
io.generation.fetch_add(landed as u64, Ordering::Release);
}
if self.orphans.is_empty() {
Poll::Ready(())
} else {
Poll::Pending
}
}
fn lent_back(&mut self, result: io::Result<()>, buf: OwnedBuf) -> 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,
addr: usize,
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<bool>> {
if self.poll_writes(io, waker, after).is_pending() {
return Poll::Pending;
}
loop {
self.drop_stale_ahead(io, after);
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 pooled = job.task.pooled();
let result = match job.task.poll(waker) {
Poll::Pending => return Poll::Pending,
Poll::Ready(result) => result,
};
tally_read(pooled, &result, |buf| buf.filled().len());
let (at, epoch, generation) = (job.at, job.epoch, job.generation);
self.reading = None;
match result {
Err(err) => return Poll::Ready(Err(err)),
Ok(buf) if !self.counts(io, at, epoch, generation) => 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);
}
self.ahead_gen = generation;
}
}
continue;
}
let (buf, from, len) = if io.direct {
match bounce(want, addr, true, waker) {
Some(got) => got,
None => return Poll::Pending,
}
} else {
let Some(buf) = buffer(want.max(READ_MIN), after) else {
return Poll::Pending;
};
let len = buf.capacity();
(buf, 0, len)
};
let generation = io.generation();
self.reading = Some(Job {
task: io.submit_read(buf, from, len, self.cursor),
at: self.cursor,
epoch: self.epoch,
generation,
});
}
}
fn write_owned(
&mut self,
io: &FileIo,
held: &mut Option<OwnedBuf>,
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, waker));
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()));
}
if io.direct {
return self.write_direct(io, bufs, waker, after);
}
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() && self.orphans.is_empty() {
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, waker));
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;
let _ = self.poll_writes(io, waker, after);
Poll::Ready(Ok(n))
}
fn write_direct(
&mut self,
io: &FileIo,
bufs: &[io::IoSlice<'_>],
waker: &Waker,
after: &mut After,
) -> Poll<io::Result<usize>> {
let Some(bytes) = bufs.iter().find(|b| !b.is_empty()) else {
return Poll::Ready(Ok(0));
};
if self.writing.is_some() || !self.orphans.is_empty() {
return Poll::Pending;
}
let Some((mut buf, from, n)) = bounce(bytes.len(), bytes.as_ptr() as usize, true, waker)
else {
return Poll::Pending;
};
buf.fill_at(from, &bytes[..n]);
self.invalidate(after);
self.writing = Some(io.submit_write(buf, self.cursor, waker));
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 = io.fd.clone();
crate::runtime::spawn_blocking(move || sys::file_size(fd.raw()))
});
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))
}
}