use std::io;
use std::sync::{Arc, Mutex, MutexGuard};
use windows_sys::Win32::Storage::FileSystem::IORING_OP_CODE;
use windows_threadpool_sys::callback_env::CallbackEnviron;
use windows_threadpool_sys::wait::{ThreadpoolWait, WaitableHandle};
use crate::batch::Batch;
use crate::capability::RingVersion;
use crate::ring::{Completion, IoRing, Op, RingInfo};
type OnCompletion<T, X> = dyn Fn(Completion, Option<(Option<T>, X)>) + Send + Sync;
fn drain<T, X>(ring: &Mutex<IoRing<T, X>>, on_completion: &OnCompletion<T, X>) {
loop {
let popped = {
let mut ring = ring
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ring.try_pop()
};
match popped {
Ok(Some((completion, held))) => on_completion(completion, held),
Ok(None) => break,
Err(error) => {
debug_assert!(
false,
"IoRing::try_pop failed during event-driven drain: {error}"
);
break;
}
}
}
}
pub struct EventDelivery<T = (), X = ()> {
wait: ThreadpoolWait,
ring: Arc<Mutex<IoRing<T, X>>>,
}
impl<T, X> Drop for EventDelivery<T, X> {
fn drop(&mut self) {
self.wait.stop_and_drain();
}
}
impl<T: Send + 'static, X: Send + 'static> EventDelivery<T, X> {
pub fn new<F>(
mut ring: IoRing<T, X>,
on_completion: F,
env: Option<&mut CallbackEnviron<'_>>,
) -> io::Result<Self>
where
F: Fn(Completion, Option<(Option<T>, X)>) + Send + Sync + 'static,
{
let event = ring.attach_completion_event_unsignalled()?;
windows_threadpool_sys::trace_record!(
"delivery",
"event-attached",
std::os::windows::io::AsRawHandle::as_raw_handle(&event) as usize
);
let event = unsafe { WaitableHandle::assume_waitable(event) };
let ring = Arc::new(Mutex::new(ring));
let ring_for_wait = Arc::clone(&ring);
let on_completion: Arc<OnCompletion<T, X>> = Arc::new(on_completion);
let wait = ThreadpoolWait::new(
event,
move |activation| {
windows_threadpool_sys::trace_record!("delivery", "callback-entered");
drain(&ring_for_wait, on_completion.as_ref());
activation.rearm(None);
drain(&ring_for_wait, on_completion.as_ref());
windows_threadpool_sys::trace_record!("delivery", "callback-left");
},
env,
)?;
wait.arm(None);
windows_threadpool_sys::trace_record!("delivery", "armed");
ring.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.raise_setup_signal()?;
Ok(Self { wait, ring })
}
#[must_use]
pub fn scope(&self) -> RingScope<'_, T, X> {
RingScope {
ring: self
.ring
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
}
}
}
pub struct RingScope<'delivery, T = (), X = ()> {
ring: MutexGuard<'delivery, IoRing<T, X>>,
}
impl<T, X> RingScope<'_, T, X> {
pub fn batch(&mut self) -> Batch<'_, T, X> {
Batch::new(&mut self.ring)
}
#[must_use]
pub fn outstanding(&self) -> usize {
self.ring.outstanding()
}
#[must_use]
pub fn version(&self) -> RingVersion {
self.ring.version()
}
pub fn info(&self) -> io::Result<RingInfo> {
self.ring.info()
}
#[must_use]
pub fn supports(&self, op: Op) -> bool {
self.ring.supports(op)
}
#[must_use]
pub fn supports_raw(&self, op_code: IORING_OP_CODE) -> bool {
self.ring.supports_raw(op_code)
}
#[must_use]
pub fn registered_file_count(&self) -> u32 {
self.ring.registered_file_count()
}
#[must_use]
pub fn registered_buffer_count(&self) -> u32 {
self.ring.registered_buffer_count()
}
}
#[cfg(test)]
mod tests;