use std::ffi::c_void;
use std::io;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::time::{Duration, Instant};
use windows_sys::Win32::Storage::FileSystem::{
CloseIoRing, CreateIoRing, GetIoRingInfo, IORING_BUFFER_INFO, IORING_CQE,
IORING_CREATE_ADVISORY_FLAGS_NONE, IORING_CREATE_FLAGS, IORING_CREATE_REQUIRED_FLAGS_NONE,
IORING_INFO, IORING_OP_CANCEL, IORING_OP_CODE, IORING_OP_FLUSH, IORING_OP_NOP, IORING_OP_READ,
IORING_OP_REGISTER_BUFFERS, IORING_OP_REGISTER_FILES, IORING_OP_WRITE, IsIoRingOpSupported,
};
use windows_sys::Win32::System::Threading::{CreateEventW, SetEvent};
use crate::accounting::Accounting;
use crate::capability::{RingVersion, capabilities};
use crate::error::check;
pub(crate) use crate::accounting::RingId;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Op {
Nop,
Read,
Write,
Flush,
RegisterFiles,
RegisterBuffers,
Cancel,
}
impl Op {
const ALL: [Op; 7] = [
Op::Nop,
Op::Read,
Op::Write,
Op::Flush,
Op::RegisterFiles,
Op::RegisterBuffers,
Op::Cancel,
];
#[must_use]
pub fn code(self) -> IORING_OP_CODE {
match self {
Op::Nop => IORING_OP_NOP,
Op::Read => IORING_OP_READ,
Op::Write => IORING_OP_WRITE,
Op::Flush => IORING_OP_FLUSH,
Op::RegisterFiles => IORING_OP_REGISTER_FILES,
Op::RegisterBuffers => IORING_OP_REGISTER_BUFFERS,
Op::Cancel => IORING_OP_CANCEL,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct OpSupport(u8);
impl OpSupport {
unsafe fn probe(handle: *mut c_void) -> Self {
let mut mask = 0_u8;
for (index, op) in Op::ALL.iter().enumerate() {
let supported = unsafe { IsIoRingOpSupported(handle, op.code()) };
if supported != 0 {
mask |= 1 << index;
}
}
Self(mask)
}
fn contains(self, op: Op) -> bool {
let index = Op::ALL
.iter()
.position(|&candidate| candidate == op)
.expect("Op::ALL is exhaustive");
self.0 & (1 << index) != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RingInfo {
pub version: RingVersion,
pub submission_queue_size: u32,
pub completion_queue_size: u32,
}
#[cfg(any(test, feature = "fault-injection"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InjectedFailure {
Ring(crate::RingCondition),
Win32(u32),
Hresult(windows_sys::core::HRESULT),
}
#[cfg(any(test, feature = "fault-injection"))]
impl InjectedFailure {
fn as_hresult(self) -> windows_sys::core::HRESULT {
let code = match self {
Self::Ring(condition) => condition.code(),
Self::Win32(code) => (0x8007_0000_u32 | (code & 0xFFFF)).cast_signed(),
Self::Hresult(code) => code,
};
assert!(
code < 0,
"an injected failure must be a failing HRESULT, but {code:#010x} is not: \
this seam injects failure only, never success"
);
code
}
}
#[derive(Clone, Copy, Debug)]
pub struct Completion {
user_data: usize,
result_code: windows_sys::core::HRESULT,
information: usize,
ring_id: RingId,
}
impl Completion {
#[must_use]
pub fn user_data(&self) -> usize {
self.user_data
}
#[must_use]
pub(crate) fn ring_id(&self) -> RingId {
self.ring_id
}
pub fn result(&self) -> io::Result<usize> {
check(self.result_code)?;
Ok(self.information)
}
#[cfg(any(test, feature = "fault-injection"))]
#[must_use]
pub fn with_injected_failure(self, failure: InjectedFailure) -> Self {
Self {
result_code: failure.as_hresult(),
information: 0,
..self
}
}
#[cfg(any(test, feature = "fault-injection"))]
#[must_use]
pub fn with_injected_transfer(self, transferred: usize) -> Self {
Self {
information: transferred,
..self
}
}
#[cfg(test)]
pub(crate) fn synthetic(
user_data: usize,
result_code: windows_sys::core::HRESULT,
ring_id: RingId,
) -> Self {
Self {
user_data,
result_code,
information: 0,
ring_id,
}
}
}
const S_FALSE: windows_sys::core::HRESULT = 1;
const RUN_DOWN_POLL_MS: u32 = 50;
const IORING_E_WAIT_TIMEOUT: windows_sys::core::HRESULT =
(0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_TIMEOUT) as windows_sys::core::HRESULT;
const MAX_WAIT_MS: u32 = u32::MAX - 1;
pub(crate) fn every_entry_was_submitted(hr: windows_sys::core::HRESULT) -> bool {
hr == IORING_E_WAIT_TIMEOUT || check(hr).is_ok()
}
fn wait_outcome(hr: windows_sys::core::HRESULT) -> io::Result<()> {
if hr == IORING_E_WAIT_TIMEOUT {
return Ok(());
}
check(hr)
}
pub struct IoRing {
handle: *mut c_void,
version: RingVersion,
supported_ops: OpSupport,
accounting: Accounting,
registered_buffer_infos: Vec<IORING_BUFFER_INFO>,
completion_event: Option<OwnedHandle>,
}
impl std::fmt::Debug for IoRing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IoRing")
.field("handle", &self.handle)
.field("version", &self.version)
.field("supported_ops", &self.supported_ops)
.field("accounting", &self.accounting)
.field(
"registered_buffer_infos",
&self.registered_buffer_infos.len(),
)
.field("completion_event", &self.completion_event)
.finish()
}
}
unsafe impl Send for IoRing {}
impl IoRing {
pub fn new(submission_queue_size: u32, completion_queue_size: u32) -> io::Result<Self> {
let caps = capabilities()?;
let version = RingVersion::HIGHEST_KNOWN.min(caps.max_version);
Self::with_version(version, submission_queue_size, completion_queue_size)
}
pub fn with_version(
version: RingVersion,
submission_queue_size: u32,
completion_queue_size: u32,
) -> io::Result<Self> {
let flags = IORING_CREATE_FLAGS {
Required: IORING_CREATE_REQUIRED_FLAGS_NONE,
Advisory: IORING_CREATE_ADVISORY_FLAGS_NONE,
};
let mut handle: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
CreateIoRing(
version.raw(),
flags,
submission_queue_size,
completion_queue_size,
&raw mut handle,
)
};
check(hr)?;
let supported_ops = unsafe { OpSupport::probe(handle) };
Ok(Self {
handle,
version,
supported_ops,
accounting: Accounting::new(),
registered_buffer_infos: Vec::new(),
completion_event: None,
})
}
#[must_use]
pub fn version(&self) -> RingVersion {
self.version
}
pub fn info(&self) -> io::Result<RingInfo> {
let mut raw = IORING_INFO::default();
let hr = unsafe { GetIoRingInfo(self.handle, &raw mut raw) };
check(hr)?;
Ok(RingInfo {
version: RingVersion::from_raw(raw.IoRingVersion),
submission_queue_size: raw.SubmissionQueueSize,
completion_queue_size: raw.CompletionQueueSize,
})
}
#[must_use]
pub fn supports(&self, op: Op) -> bool {
self.supported_ops.contains(op)
}
#[cfg(test)]
pub(crate) fn set_supported_ops_for_test(&mut self, ops: &[Op]) {
self.supported_ops = OpSupport(ops.iter().fold(0_u8, |mask, &op| {
let index = Op::ALL
.iter()
.position(|&candidate| candidate == op)
.expect("Op::ALL is exhaustive");
mask | (1 << index)
}));
}
#[cfg_attr(
feature = "threadpool",
doc = "[`EventDelivery`](crate::EventDelivery) already does this for you and"
)]
#[cfg_attr(
not(feature = "threadpool"),
doc = "`EventDelivery` (the default `threadpool` feature) already does this for you and"
)]
pub fn completion_event(&mut self) -> io::Result<OwnedHandle> {
let (event, owes_setup_signal) = self.attach_completion_event_unsignalled()?;
if owes_setup_signal {
self.raise_setup_signal()?;
}
Ok(event)
}
pub(crate) fn attach_completion_event_unsignalled(
&mut self,
) -> io::Result<(OwnedHandle, bool)> {
if let Some(event) = &self.completion_event {
return Ok((event.try_clone()?, false));
}
if !capabilities()?.supports_completion_event {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"this system's IoRing does not report IORING_FEATURE_SET_COMPLETION_EVENT",
));
}
let raw = unsafe { CreateEventW(std::ptr::null(), 0, 0, std::ptr::null()) };
if raw.is_null() {
return Err(io::Error::last_os_error());
}
let event = unsafe { OwnedHandle::from_raw_handle(raw) };
let hr = unsafe { crate::sys::set_completion_event(self.handle, event.as_raw_handle()) };
check(hr)?;
self.completion_event
.insert(event)
.try_clone()
.map(|dup| (dup, true))
}
pub(crate) fn raise_setup_signal(&self) -> io::Result<()> {
let Some(event) = &self.completion_event else {
return Ok(());
};
if unsafe { SetEvent(event.as_raw_handle()) } == 0 {
return Err(io::Error::last_os_error());
}
#[cfg(feature = "threadpool")]
windows_threadpool_sys::trace_record!(
"delivery",
"setup-signalled",
event.as_raw_handle() as usize,
self.accounting.outstanding()
);
Ok(())
}
#[must_use]
pub fn supports_raw(&self, op_code: IORING_OP_CODE) -> bool {
unsafe { IsIoRingOpSupported(self.handle, op_code) != 0 }
}
#[must_use]
pub fn registered_file_count(&self) -> u32 {
self.accounting.registered_file_count()
}
#[must_use]
pub fn registered_buffer_count(&self) -> u32 {
self.accounting.registered_buffer_count()
}
pub(crate) fn reserve_registered_files(&mut self, count: u32) {
self.accounting.reserve_registered_files(count);
}
pub(crate) fn reserve_registered_buffers(&mut self, count: u32) {
self.accounting.reserve_registered_buffers(count);
}
pub(crate) fn hold_registered_buffer_infos(
&mut self,
infos: Vec<IORING_BUFFER_INFO>,
) -> *const IORING_BUFFER_INFO {
debug_assert!(
self.registered_buffer_infos.is_empty(),
"a ring accepts at most one buffer registration, so this must only be set once"
);
self.registered_buffer_infos = infos;
self.registered_buffer_infos.as_ptr()
}
#[must_use]
pub fn outstanding(&self) -> usize {
self.accounting.outstanding()
}
pub(crate) fn reserve_user_data(&mut self) -> io::Result<usize> {
self.accounting.reserve_user_data()
}
pub(crate) fn record_completion(&mut self) {
self.accounting.record_completion();
}
pub(crate) fn cancel_reservation(&mut self) {
self.accounting.cancel_reservation();
}
pub(crate) fn accounting_mut(&mut self) -> &mut Accounting {
&mut self.accounting
}
pub(crate) fn raw_handle(&self) -> *mut c_void {
self.handle
}
pub(crate) fn ring_id(&self) -> RingId {
self.accounting.ring_id()
}
pub unsafe fn push_raw(
&mut self,
build: impl FnOnce(*mut c_void, usize) -> windows_sys::core::HRESULT,
) -> io::Result<usize> {
let user_data = self.reserve_user_data()?;
let hr = build(self.handle, user_data);
if let Err(error) = check(hr) {
self.cancel_reservation();
return Err(error);
}
Ok(user_data)
}
pub fn run_down(&mut self) -> io::Result<()> {
while !self.run_down_within(Duration::MAX)? {}
Ok(())
}
pub fn run_down_within(&mut self, bound: Duration) -> io::Result<bool> {
let deadline = Instant::now().checked_add(bound);
loop {
if self.accounting.outstanding() == 0 {
return Ok(true);
}
let remaining = match deadline {
Some(deadline) => deadline.saturating_duration_since(Instant::now()),
None => Duration::MAX,
};
if remaining.is_zero() {
return Ok(false);
}
let poll_ms = u32::try_from(remaining.as_millis())
.unwrap_or(u32::MAX)
.clamp(1, RUN_DOWN_POLL_MS);
let mut submitted = 0_u32;
let hr = unsafe { crate::sys::submit(self.handle, 1, poll_ms, &raw mut submitted) };
wait_outcome(hr)?;
self.drain_for_rundown()?;
}
}
fn drain_for_rundown(&mut self) -> io::Result<()> {
while self.try_pop()?.is_some() {}
Ok(())
}
pub fn try_pop(&mut self) -> io::Result<Option<Completion>> {
let mut cqe = IORING_CQE {
UserData: 0,
ResultCode: 0,
Information: 0,
};
let hr = unsafe { crate::sys::pop(self.handle, &raw mut cqe) };
if hr == S_FALSE {
return Ok(None);
}
check(hr)?;
self.record_completion();
Ok(Some(Completion {
user_data: cqe.UserData,
result_code: cqe.ResultCode,
information: cqe.Information,
ring_id: self.accounting.ring_id(),
}))
}
pub fn pop_within(&mut self, timeout: Duration) -> io::Result<Option<Completion>> {
self.pop_within_with(&mut SubmitWait, timeout)
}
pub fn pop_within_with<W: CompletionWait + ?Sized>(
&mut self,
wait: &mut W,
timeout: Duration,
) -> io::Result<Option<Completion>> {
let deadline = Instant::now().checked_add(timeout);
loop {
if let Some(completion) = self.try_pop()? {
return Ok(Some(completion));
}
if self.outstanding() == 0 {
return Ok(None);
}
let remaining = match deadline {
Some(deadline) => deadline.saturating_duration_since(Instant::now()),
None => Duration::MAX,
};
if remaining.is_zero() {
return Ok(None);
}
let ms = u32::try_from(remaining.as_millis())
.unwrap_or(MAX_WAIT_MS)
.clamp(1, MAX_WAIT_MS);
wait.wait(&mut RingWait { ring: self }, ms)?;
}
}
}
pub trait CompletionWait {
fn wait(&mut self, ring: &mut RingWait<'_>, timeout_ms: u32) -> io::Result<()>;
}
#[cfg_attr(
feature = "threadpool",
doc = "[`RingScope`](crate::RingScope) under [D-43](../DESIGN-NOTES.md#d-43)."
)]
#[cfg_attr(
not(feature = "threadpool"),
doc = "`RingScope` (the default `threadpool` feature) under [D-43](../DESIGN-NOTES.md#d-43)."
)]
pub struct RingWait<'ring> {
ring: &'ring mut IoRing,
}
impl RingWait<'_> {
pub fn block(&mut self, timeout_ms: u32) -> io::Result<()> {
let mut submitted = 0_u32;
let hr = unsafe { crate::sys::submit(self.ring.handle, 1, timeout_ms, &raw mut submitted) };
wait_outcome(hr)
}
#[must_use]
pub fn outstanding(&self) -> usize {
self.ring.outstanding()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SubmitWait;
impl CompletionWait for SubmitWait {
fn wait(&mut self, ring: &mut RingWait<'_>, timeout_ms: u32) -> io::Result<()> {
ring.block(timeout_ms)
}
}
#[cfg(test)]
impl IoRing {
fn refused_by_the_kernel() -> Self {
Self {
handle: std::ptr::null_mut(),
version: RingVersion::V1,
supported_ops: OpSupport::default(),
accounting: Accounting::new(),
registered_buffer_infos: Vec::new(),
completion_event: None,
}
}
}
impl Drop for IoRing {
fn drop(&mut self) {
#[cfg(test)]
DROP_RUNS.with(|runs| runs.set(runs.get() + 1));
if let Err(error) = self.run_down() {
debug_assert!(
std::thread::panicking(),
"IoRing rundown failed before close: {error}"
);
}
let hr = unsafe { CloseIoRing(self.handle) };
debug_assert!(
hr >= 0 || std::thread::panicking(),
"CloseIoRing failed: 0x{:08X}",
hr as u32
);
}
}
#[cfg(test)]
thread_local! {
pub(crate) static DROP_RUNS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn pop_within(ring: &mut IoRing, what: &str) -> Completion {
const BOUND: std::time::Duration = std::time::Duration::from_secs(30);
ring.pop_within(BOUND)
.expect("pop")
.unwrap_or_else(|| panic!("timed out after {BOUND:?} waiting for {what}"))
}
#[cfg(test)]
mod tests;