use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::{Poll, Waker};
use std::io;
use std::sync::Arc;
use crate::buf::PoolBuf;
use crate::buf::owned::OwnedBuf;
use crate::rt::shard::Shared;
use crate::sync::atomic_waker::AtomicWaker;
pub(crate) enum FileOutcome {
#[cfg(ring)]
Read(io::Result<PoolBuf>),
#[cfg(ring)]
ReadLent(io::Result<usize>, OwnedBuf),
Write(io::Result<()>),
Copied(io::Result<u64>),
WriteLent(io::Result<()>, OwnedBuf),
Append(io::Result<u64>),
AppendLent(io::Result<u64>, OwnedBuf),
#[cfg(ring)]
Sync(io::Result<()>),
}
pub(crate) struct Slot {
full: AtomicBool,
outcome: UnsafeCell<Option<FileOutcome>>,
waker: AtomicWaker,
}
unsafe impl Send for Slot {}
unsafe impl Sync for Slot {}
const SPARE_SLOTS: usize = 8;
thread_local! {
static SPARES: core::cell::RefCell<Vec<Arc<Slot>>> =
const { core::cell::RefCell::new(Vec::new()) };
}
impl Slot {
fn fresh() -> Self {
Self {
full: AtomicBool::new(false),
outcome: UnsafeCell::new(None),
waker: AtomicWaker::new(),
}
}
pub(crate) fn new() -> Arc<Self> {
let spare = SPARES.try_with(|spares| {
let mut spares = spares.try_borrow_mut().ok()?;
let at = spares
.iter_mut()
.position(|slot| Arc::get_mut(slot).is_some())?;
let mut slot = spares.swap_remove(at);
if let Some(only) = Arc::get_mut(&mut slot) {
*only = Self::fresh();
}
Some(slot)
});
spare
.ok()
.flatten()
.unwrap_or_else(|| Arc::new(Self::fresh()))
}
fn spare(slot: Arc<Self>) {
let _ = SPARES.try_with(|spares| {
if let Ok(mut spares) = spares.try_borrow_mut()
&& spares.len() < SPARE_SLOTS
{
if spares.capacity() == 0 {
spares.reserve_exact(SPARE_SLOTS);
}
spares.push(slot);
}
});
}
#[cfg(ring)]
pub(crate) fn register(&self, waker: &Waker) {
self.waker.register(waker);
}
#[cfg(ring)]
pub(crate) fn put(&self, outcome: FileOutcome) {
if let Some(waker) = self.fill(outcome) {
waker.wake();
}
}
fn fill(&self, outcome: FileOutcome) -> Option<Waker> {
unsafe { *self.outcome.get() = Some(outcome) };
self.full.store(true, Ordering::Release);
self.waker.take()
}
pub(crate) fn take(&self) -> Option<FileOutcome> {
if !self.full.load(Ordering::Acquire) {
return None;
}
unsafe { (*self.outcome.get()).take() }
}
}
pub(crate) type LentReadEnd = (io::Result<usize>, OwnedBuf);
pub(crate) type LentWriteEnd = (io::Result<()>, OwnedBuf);
pub(crate) type LentAppendEnd = (io::Result<u64>, OwnedBuf);
pub(crate) struct SlotJob<T> {
slot: Option<Arc<Slot>>,
end: PhantomData<fn() -> T>,
}
impl<T> SlotJob<T>
where
T: End,
{
pub(crate) fn new(slot: Arc<Slot>) -> Self {
Self {
slot: Some(slot),
end: PhantomData,
}
}
pub(crate) fn poll(&mut self, waker: &Waker) -> Poll<T> {
let Some(slot) = &self.slot else {
return Poll::Pending;
};
if let Some(outcome) = slot.take() {
return Poll::Ready(T::of(outcome));
}
slot.waker.register(waker);
match slot.take() {
Some(outcome) => Poll::Ready(T::of(outcome)),
None => Poll::Pending,
}
}
}
impl<T> Drop for SlotJob<T> {
fn drop(&mut self) {
if let Some(slot) = self.slot.take() {
Slot::spare(slot);
}
}
}
pub(crate) struct WriteCall(Arc<Shared>);
impl WriteCall {
pub(crate) fn on(shard: &Arc<Shared>) -> Self {
shard.write_began();
Self(Arc::clone(shard))
}
pub(crate) fn here() -> Option<Self> {
crate::rt::context::current(|current| current.map(|shard| Self::on(&shard.shared)))
}
}
impl Drop for WriteCall {
fn drop(&mut self) {
self.0.write_ended();
}
}
pub(crate) fn write_on_pool<T, F>(call: F, waker: &Waker) -> SlotJob<T>
where
T: End,
F: FnOnce() -> FileOutcome + Send + 'static,
{
let slot = Slot::new();
slot.waker.register(waker);
let write = WriteCall::here();
let end = Arc::clone(&slot);
crate::runtime::spawn_blocking(move || Ending::new(end, call(), write)).detach();
SlotJob::new(slot)
}
pub(crate) struct Ending {
end: Option<(Arc<Slot>, FileOutcome)>,
write: Option<WriteCall>,
}
impl Ending {
pub(crate) fn new(slot: Arc<Slot>, outcome: FileOutcome, write: Option<WriteCall>) -> Self {
Self {
end: Some((slot, outcome)),
write,
}
}
}
impl Drop for Ending {
fn drop(&mut self) {
let Some((slot, outcome)) = self.end.take() else {
return;
};
let waker = slot.fill(outcome);
drop(slot);
if let Some(waker) = waker {
waker.wake();
}
drop(self.write.take());
}
}
pub(crate) trait End: Sized {
fn of(outcome: FileOutcome) -> Self;
}
#[cold]
fn mismatched() -> io::Error {
io::Error::other("a file call ended as another kind of call")
}
impl End for io::Result<PoolBuf> {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
#[cfg(ring)]
FileOutcome::Read(result) => result,
_ => Err(mismatched()),
}
}
}
impl End for io::Result<()> {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
FileOutcome::Write(result) => result,
#[cfg(ring)]
FileOutcome::Sync(result) => result,
_ => Err(mismatched()),
}
}
}
impl End for io::Result<u64> {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
FileOutcome::Append(result) | FileOutcome::Copied(result) => result,
_ => Err(mismatched()),
}
}
}
impl End for LentReadEnd {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
#[cfg(ring)]
FileOutcome::ReadLent(result, buf) => (result, buf),
_ => (Err(mismatched()), OwnedBuf::default()),
}
}
}
impl End for LentWriteEnd {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
FileOutcome::WriteLent(result, buf) => (result, buf),
_ => (Err(mismatched()), OwnedBuf::default()),
}
}
}
impl End for LentAppendEnd {
#[inline]
fn of(outcome: FileOutcome) -> Self {
match outcome {
FileOutcome::AppendLent(result, buf) => (result, buf),
_ => (Err(mismatched()), OwnedBuf::default()),
}
}
}