use std::collections::VecDeque;
use std::ffi::c_void;
use std::sync::{Arc, Mutex, PoisonError};
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLUSH_MODE, IORING_BUFFER_INFO, IORING_BUFFER_REF, IORING_CQE, IORING_HANDLE_REF,
IOSQE_FLAGS_DRAIN_PRECEDING_OPS,
};
use windows_sys::Win32::System::Threading::SetEvent;
use windows_sys::core::HRESULT;
use super::Responses;
use crate::IoRing;
pub const SEED_VAR: &str = "WINDOWS_IORING_RESOLVER_SEED";
const MAX_DEFERRALS: u32 = 4;
const MAX_CONSECUTIVE_SUBMIT_FAILURES: u32 = 2;
const S_OK: HRESULT = 0;
const S_FALSE: HRESULT = 1;
const IORING_E_WAIT_TIMEOUT: HRESULT =
(0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_TIMEOUT) as HRESULT;
const SUBMIT_FAILED: HRESULT =
(0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_NOT_ENOUGH_MEMORY) as HRESULT;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolverConfig {
pub may_pend: bool,
pub may_reorder: bool,
pub may_fail_operations: bool,
pub may_expire_waits: bool,
pub may_wake_empty: bool,
pub edge_triggered_signal: bool,
pub may_fail_submits: bool,
pub may_transfer_partially: bool,
}
impl Default for ResolverConfig {
fn default() -> Self {
Self {
may_pend: true,
may_reorder: true,
may_fail_operations: true,
may_expire_waits: true,
may_wake_empty: true,
edge_triggered_signal: true,
may_fail_submits: true,
may_transfer_partially: true,
}
}
}
impl ResolverConfig {
#[must_use]
pub const fn narrowest() -> Self {
Self {
may_pend: false,
may_reorder: false,
may_fail_operations: false,
may_expire_waits: false,
may_wake_empty: false,
edge_triggered_signal: true,
may_fail_submits: false,
may_transfer_partially: false,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct ResolverStats {
pub built: usize,
pub submitted: usize,
pub posted: usize,
pub popped: usize,
pub failed_operations: usize,
pub failed_submits: usize,
pub expired_waits: usize,
pub empty_wakes: usize,
pub signals: usize,
pub reorderings: usize,
pub deferrals: usize,
pub barrier_holds: usize,
pub partial_transfers: usize,
}
#[derive(Debug, Clone)]
pub struct ResolverWatch(Arc<Mutex<ResolverStats>>);
impl ResolverWatch {
#[must_use]
pub fn stats(&self) -> ResolverStats {
*self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
}
#[derive(Debug, Clone, Copy)]
struct CompletionEvent(*mut c_void);
unsafe impl Send for CompletionEvent {}
#[derive(Debug, Clone, Copy)]
struct Op {
user_data: usize,
seq: u64,
barrier: bool,
deferrals: u32,
requested: Option<u32>,
}
pub struct Resolver {
seed: u64,
state: u64,
config: ResolverConfig,
staged: Vec<Op>,
pool: Vec<Op>,
posted: VecDeque<(usize, HRESULT, usize)>,
next_seq: u64,
consecutive_submit_failures: u32,
event: CompletionEvent,
stats: Arc<Mutex<ResolverStats>>,
}
impl std::fmt::Debug for Resolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Resolver")
.field("seed", &format_args!("0x{:016X}", self.seed))
.field("config", &self.config)
.field("staged", &self.staged.len())
.field("pool", &self.pool.len())
.field("posted", &self.posted.len())
.field(
"stats",
&*self.stats.lock().unwrap_or_else(PoisonError::into_inner),
)
.finish()
}
}
impl Resolver {
#[must_use]
pub fn new(seed: u64) -> Self {
Self::with_config(seed, ResolverConfig::default())
}
#[must_use]
pub fn with_config(seed: u64, config: ResolverConfig) -> Self {
Self {
seed,
state: seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1,
config,
staged: Vec::new(),
pool: Vec::new(),
posted: VecDeque::new(),
next_seq: 0,
consecutive_submit_failures: 0,
event: CompletionEvent(std::ptr::null_mut()),
stats: Arc::new(Mutex::new(ResolverStats::default())),
}
}
#[must_use]
pub fn seed(&self) -> u64 {
self.seed
}
#[must_use]
pub fn watch(&self) -> ResolverWatch {
ResolverWatch(Arc::clone(&self.stats))
}
#[must_use]
pub fn seed_from_env() -> u64 {
match std::env::var(SEED_VAR) {
Ok(text) => {
let trimmed = text.trim();
trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
.map_or_else(
|| trimmed.parse::<u64>().ok(),
|hex| u64::from_str_radix(hex, 16).ok(),
)
.unwrap_or_else(|| {
panic!("{SEED_VAR} is set to {text:?}, which is neither decimal nor 0x hex")
})
}
Err(_) => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0x243F_6A88_85A3_08D3, |elapsed| elapsed.as_nanos() as u64),
}
}
#[must_use]
pub fn replay_hint(&self) -> String {
format!("$env:{SEED_VAR}='0x{:016X}'", self.seed)
}
pub fn register<T, X, K>(self, ring: &IoRing<T, X, K>) -> ResolverWatch {
let watch = self.watch();
let _ = super::register(ring, Box::new(self));
watch
}
fn next(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn chance(&mut self, percent: u64) -> bool {
self.next() % 100 < percent
}
fn record(&self, f: impl FnOnce(&mut ResolverStats)) {
f(&mut self.stats.lock().unwrap_or_else(PoisonError::into_inner));
}
fn build(&mut self, user_data: usize, flags: i32, requested: Option<u32>) -> HRESULT {
let seq = self.next_seq;
self.next_seq += 1;
self.staged.push(Op {
user_data,
seq,
barrier: flags & IOSQE_FLAGS_DRAIN_PRECEDING_OPS != 0,
deferrals: 0,
requested,
});
self.record(|s| s.built += 1);
S_OK
}
fn post_one(&mut self) -> bool {
if self.pool.is_empty() {
return false;
}
debug_assert!(
self.pool.windows(2).all(|pair| pair[0].seq < pair[1].seq),
"the pool must stay in build order or RS-C-4's positional test is meaningless"
);
let eligible: Vec<usize> = (0..self.pool.len())
.filter(|&i| i == 0 || !self.pool[i].barrier)
.collect();
let held = self.pool.len() - eligible.len();
if held > 0 {
self.record(|s| s.barrier_holds += 1);
}
let pick = if self.config.may_reorder {
let draw = self.next() % eligible.len() as u64;
eligible[usize::try_from(draw).expect("a modulus of a usize length fits a usize")]
} else {
eligible[0]
};
if pick != 0 {
self.record(|s| s.reorderings += 1);
}
let op = self.pool.remove(pick);
let result = if self.config.may_fail_operations && self.chance(20) {
self.record(|s| s.failed_operations += 1);
let code = self.next() & 0xFFFF;
(0x8007_0000_u32 | u32::try_from(code).expect("masked to 16 bits")) as HRESULT
} else {
S_OK
};
let was_empty = self.posted.is_empty();
let information = match (result == S_OK, op.requested) {
(true, Some(requested)) => {
if self.config.may_transfer_partially && requested > 0 && self.chance(8) {
self.record(|s| s.partial_transfers += 1);
let short = self.next() % u64::from(requested);
usize::try_from(short).expect("short count fits, it is below a u32")
} else {
usize::try_from(requested).expect("a u32 fits a usize on this target")
}
}
_ => 0,
};
self.posted.push_back((op.user_data, result, information));
self.record(|s| s.posted += 1);
if !self.event.0.is_null() && (was_empty || !self.config.edge_triggered_signal) {
self.record(|s| s.signals += 1);
unsafe { SetEvent(self.event.0) };
}
true
}
fn tick(&mut self, flip: bool) {
if self.pool.is_empty() {
return;
}
let mut want = 0_usize;
for i in 0..self.pool.len() {
let forced = self.pool[i].deferrals >= MAX_DEFERRALS;
if forced || (flip && (!self.config.may_pend || self.chance(50))) {
want += 1;
}
}
let deferred = self.pool.len() - want.min(self.pool.len());
if deferred > 0 {
self.record(|s| s.deferrals += deferred);
}
for op in &mut self.pool {
op.deferrals += 1;
}
for _ in 0..want {
if !self.post_one() {
break;
}
}
}
}
impl Responses for Resolver {
unsafe fn submit(
&mut self,
_ring: *mut c_void,
wait_operations: u32,
_milliseconds: u32,
submitted: *mut u32,
) -> HRESULT {
if self.config.may_fail_submits
&& !self.staged.is_empty()
&& self.consecutive_submit_failures < MAX_CONSECUTIVE_SUBMIT_FAILURES
&& self.chance(15)
{
self.consecutive_submit_failures += 1;
self.record(|s| s.failed_submits += 1);
unsafe { submitted.write(0) };
return SUBMIT_FAILED;
}
self.consecutive_submit_failures = 0;
let moved = self.staged.len();
self.pool.append(&mut self.staged);
self.record(|s| s.submitted += moved);
unsafe {
submitted.write(u32::try_from(moved).unwrap_or(u32::MAX));
}
self.tick(true);
if wait_operations == 0 {
return S_OK;
}
if self.config.may_expire_waits && self.chance(25) {
self.record(|s| s.expired_waits += 1);
return IORING_E_WAIT_TIMEOUT;
}
if self.posted.is_empty() {
self.record(|s| s.empty_wakes += 1);
}
S_OK
}
unsafe fn pop(&mut self, _ring: *mut c_void, cqe: *mut IORING_CQE) -> HRESULT {
if self.posted.is_empty() {
self.tick(false);
}
let Some((user_data, result, information)) = self.posted.pop_front() else {
return S_FALSE;
};
self.record(|s| s.popped += 1);
unsafe {
cqe.write(IORING_CQE {
UserData: user_data,
ResultCode: result,
Information: information,
});
}
S_OK
}
unsafe fn build_read(
&mut self,
_ring: *mut c_void,
_file: IORING_HANDLE_REF,
_buffer: IORING_BUFFER_REF,
bytes: u32,
_offset: u64,
user_data: usize,
flags: i32,
) -> HRESULT {
self.build(user_data, flags, Some(bytes))
}
unsafe fn build_write(
&mut self,
_ring: *mut c_void,
_file: IORING_HANDLE_REF,
_buffer: IORING_BUFFER_REF,
bytes: u32,
_offset: u64,
_caching: i32,
user_data: usize,
flags: i32,
) -> HRESULT {
self.build(user_data, flags, Some(bytes))
}
unsafe fn build_flush(
&mut self,
_ring: *mut c_void,
_file: IORING_HANDLE_REF,
_mode: FILE_FLUSH_MODE,
user_data: usize,
flags: i32,
) -> HRESULT {
self.build(user_data, flags, None)
}
unsafe fn build_cancel(
&mut self,
_ring: *mut c_void,
_file: IORING_HANDLE_REF,
_cancel_user_data: usize,
user_data: usize,
) -> HRESULT {
self.build(user_data, 0, None)
}
unsafe fn build_register_files(
&mut self,
_ring: *mut c_void,
_count: u32,
_handles: *const *mut c_void,
user_data: usize,
) -> HRESULT {
self.build(user_data, 0, None)
}
unsafe fn build_register_buffers(
&mut self,
_ring: *mut c_void,
_count: u32,
_buffers: *const IORING_BUFFER_INFO,
user_data: usize,
) -> HRESULT {
self.build(user_data, 0, None)
}
unsafe fn set_completion_event(&mut self, _ring: *mut c_void, event: *mut c_void) -> HRESULT {
self.event = CompletionEvent(event);
S_OK
}
}
#[cfg(test)]
mod tests;