use core::future::{Future, poll_fn};
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use std::io;
use std::sync::Arc;
use std::task::Wake;
use super::{FILE_SLOTS, FileIo};
#[cfg(ring)]
use crate::fs::call::SlotJob;
use crate::io::side::{After, Hooks, Side, WakerSet};
use crate::io::source::Failure;
use crate::io::source::requests::Lease;
use crate::rt::task::Join;
#[derive(Clone, Copy)]
pub(crate) enum SyncKind {
All,
Data,
Range {
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
offset: u64,
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
len: u64,
},
}
impl FileIo {
pub(crate) async fn sync(self: &Arc<Self>, kind: SyncKind) -> io::Result<()> {
poll_fn(|cx| self.poll_flush(cx)).await?;
let mut mine = 0;
poll_fn(|cx| self.lost.poll_sync(self, kind, &mut mine, cx.waker())).await
}
#[cfg(test)]
pub(crate) fn sync_calls_made(&self) -> u64 {
self.lost.made.load(core::sync::atomic::Ordering::Relaxed)
}
#[cfg(test)]
pub(crate) fn fail_next_syncs(&self, n: u32) {
self.lost
.fail_syncs
.store(n, core::sync::atomic::Ordering::Relaxed);
}
fn sync_call(&self, kind: SyncKind) -> SyncCall {
#[cfg(ring)]
if self.on_ring() {
let job = match kind {
SyncKind::All => crate::fs::ring::sync(&self.fd, false),
SyncKind::Data => crate::fs::ring::sync(&self.fd, true),
SyncKind::Range { offset, len } => match u32::try_from(len) {
Ok(len) => crate::fs::ring::sync_range(&self.fd, offset, len),
Err(_) => Err(()),
},
};
if let Ok(job) = job {
return SyncCall::Slot(job);
}
}
let fd = self.fd.clone();
#[cfg(test)]
let fails = self.lost.sync_fails();
SyncCall::Pool(
crate::runtime::spawn_blocking(move || {
#[cfg(test)]
if fails {
return Err(io::Error::other("the device lost a write-back"));
}
sync_on(&fd, kind)
})
.join(),
)
}
}
#[derive(Default)]
pub(super) struct Syncs {
out: Option<(u64, SyncCall)>,
made: u64,
ended: u64,
failed: Option<(u64, Failure)>,
}
pub(crate) struct Lost {
failure: std::sync::OnceLock<Failure>,
calls: Side<Syncs>,
waiters: WakerSet<FILE_SLOTS>,
#[cfg(test)]
made: core::sync::atomic::AtomicU64,
#[cfg(test)]
fail_syncs: core::sync::atomic::AtomicU32,
}
impl Default for Lost {
fn default() -> Self {
Self {
failure: std::sync::OnceLock::new(),
calls: Side::new(Syncs::default()),
waiters: WakerSet::new(),
#[cfg(test)]
made: core::sync::atomic::AtomicU64::new(0),
#[cfg(test)]
fail_syncs: core::sync::atomic::AtomicU32::new(0),
}
}
}
impl Lost {
pub(crate) fn keep(&self, failure: Failure) {
let _ = self.failure.set(failure);
}
pub(crate) fn get(&self) -> Option<Failure> {
self.failure.get().copied()
}
fn poll_sync(
self: &Arc<Self>,
io: &FileIo,
kind: SyncKind,
mine: &mut u64,
task: &Waker,
) -> Poll<io::Result<()>> {
self.waiters.register(task);
let waker = Waker::from(Arc::clone(self));
let mut after = After::default();
let out = self.calls.poll(&mut after, task, &CallHooks, |calls, _| {
calls.poll(self, io, kind, mine, &waker)
});
after.run();
out
}
#[cfg(test)]
fn sync_fails(&self) -> bool {
self.fail_syncs
.try_update(
core::sync::atomic::Ordering::Relaxed,
core::sync::atomic::Ordering::Relaxed,
|n| n.checked_sub(1),
)
.is_ok()
}
}
impl Wake for Lost {
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();
}
}
struct CallHooks;
impl Hooks<Syncs> for CallHooks {
fn serve(&self, _st: &mut Syncs, _after: &mut After) {}
fn releases_inline(&self) -> bool {
true
}
}
enum SyncCall {
Pool(Join<io::Result<()>>),
#[cfg(ring)]
Slot(SlotJob<io::Result<()>>),
}
impl SyncCall {
fn poll(&mut self, waker: &Waker) -> Poll<io::Result<()>> {
let end = match self {
SyncCall::Pool(job) => match Pin::new(job).poll(&mut Context::from_waker(waker)) {
Poll::Ready(Ok(end)) => end,
Poll::Ready(Err(lost)) => return Poll::Ready(Err(lost.into())),
Poll::Pending => return Poll::Pending,
},
#[cfg(ring)]
SyncCall::Slot(job) => core::task::ready!(job.poll(waker)),
};
Poll::Ready(end.inspect_err(crate::rt::counters::io_error))
}
}
impl Syncs {
pub(super) fn poll(
&mut self,
lost: &Lost,
io: &FileIo,
kind: SyncKind,
mine: &mut u64,
waker: &Waker,
) -> Poll<io::Result<()>> {
loop {
if *mine != 0 && self.ended >= *mine {
return Poll::Ready(match self.failed {
Some((at, failure)) if at <= *mine => Err(failure.error()),
_ => Ok(()),
});
}
if self.out.is_some() {
core::task::ready!(self.take(lost, waker));
continue;
}
if let Some(failure) = lost.get() {
return Poll::Ready(Err(failure.error()));
}
self.made += 1;
#[cfg(test)]
lost.made
.store(self.made, core::sync::atomic::Ordering::Relaxed);
*mine = self.made;
self.out = Some((self.made, io.sync_call(kind)));
}
}
fn take(&mut self, lost: &Lost, waker: &Waker) -> Poll<()> {
let Some((at, call)) = self.out.as_mut() else {
return Poll::Ready(());
};
let end = core::task::ready!(call.poll(waker));
self.ended = *at;
if let Err(err) = end
&& self.failed.is_none()
{
let failure = Failure::of(&err);
self.failed = Some((*at, failure));
lost.keep(failure);
}
self.out = None;
Poll::Ready(())
}
}
pub(crate) fn sync_on(fd: &Lease, kind: SyncKind) -> io::Result<()> {
sync_file(&fd.file(), kind)
}
pub(crate) fn sync_file(file: &std::fs::File, kind: SyncKind) -> io::Result<()> {
match kind {
SyncKind::All => file.sync_all(),
#[cfg(not(target_os = "linux"))]
SyncKind::Data | SyncKind::Range { .. } => file.sync_data(),
#[cfg(target_os = "linux")]
SyncKind::Data => file.sync_data(),
#[cfg(target_os = "linux")]
SyncKind::Range { offset, len } => {
use std::os::fd::AsRawFd;
crate::io::sys::sync_file_range(file.as_raw_fd(), offset, len)
}
}
}