use std::collections::HashMap;
use std::ffi::c_void;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::thread::ThreadId;
use std::time::{Duration, Instant};
use windows_sys::Win32::Foundation::S_OK;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLUSH_MODE, IORING_BUFFER_INFO, IORING_BUFFER_REF, IORING_CQE, IORING_HANDLE_REF,
};
use windows_sys::core::HRESULT;
use super::Responses;
use crate::InjectedFailure;
use crate::ring::IORING_E_WAIT_TIMEOUT;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Call {
Submit,
Pop,
BuildRead,
BuildWrite,
BuildFlush,
BuildCancel,
BuildRegisterFiles,
BuildRegisterBuffers,
SetCompletionEvent,
}
#[derive(Debug, Clone, Copy)]
struct Fail {
code: HRESULT,
remaining: Option<u32>,
}
impl Fail {
fn spend(slot: &mut Option<Self>) -> Option<HRESULT> {
let fail = (*slot)?;
*slot = match fail.remaining {
Some(left) if left > 1 => Some(Self {
remaining: Some(left - 1),
..fail
}),
Some(_) => None,
None => Some(fail),
};
Some(fail.code)
}
}
#[derive(Debug, Clone, Copy)]
enum Stall {
Off,
For(Duration),
Since(Instant, Duration),
}
#[derive(Debug)]
struct State {
calls: HashMap<Call, Fail>,
completions: Option<Fail>,
stall: Stall,
failed_from: Vec<(Call, ThreadId)>,
}
type Shared = Arc<Mutex<State>>;
fn lock(shared: &Shared) -> MutexGuard<'_, State> {
shared.lock().unwrap_or_else(PoisonError::into_inner)
}
struct Forward;
impl Responses for Forward {}
#[derive(Debug)]
pub struct CallFaults {
shared: Shared,
}
#[derive(Debug, Clone)]
pub struct CallFaultControl {
shared: Shared,
}
#[must_use]
pub fn call_faults() -> (CallFaults, CallFaultControl) {
let shared = Arc::new(Mutex::new(State {
calls: HashMap::new(),
completions: None,
stall: Stall::Off,
failed_from: Vec::new(),
}));
(
CallFaults {
shared: Arc::clone(&shared),
},
CallFaultControl { shared },
)
}
impl CallFaultControl {
pub fn fail_next(&self, call: Call, count: u32, failure: InjectedFailure) {
let code = failure.as_hresult();
let mut state = lock(&self.shared);
if count == 0 {
state.calls.remove(&call);
} else {
state.calls.insert(
call,
Fail {
code,
remaining: Some(count),
},
);
}
}
pub fn fail_until_cleared(&self, call: Call, failure: InjectedFailure) {
let code = failure.as_hresult();
lock(&self.shared).calls.insert(
call,
Fail {
code,
remaining: None,
},
);
}
pub fn fail_next_completions(&self, count: u32, failure: InjectedFailure) {
let code = failure.as_hresult();
lock(&self.shared).completions = (count != 0).then_some(Fail {
code,
remaining: Some(count),
});
}
pub fn stall_submits(&self, duration: Duration) {
lock(&self.shared).stall = if duration.is_zero() {
Stall::Off
} else {
Stall::For(duration)
};
}
pub fn clear(&self) {
let mut state = lock(&self.shared);
state.calls.clear();
state.completions = None;
state.stall = Stall::Off;
}
#[must_use]
pub fn failures(&self, call: Call) -> Vec<ThreadId> {
lock(&self.shared)
.failed_from
.iter()
.filter(|(failed, _)| *failed == call)
.map(|(_, thread)| *thread)
.collect()
}
}
impl CallFaults {
fn fails(&self, call: Call) -> Option<HRESULT> {
let mut state = lock(&self.shared);
let mut slot = state.calls.get(&call).copied();
let code = Fail::spend(&mut slot)?;
match slot {
Some(armed) => state.calls.insert(call, armed),
None => state.calls.remove(&call),
};
state.failed_from.push((call, std::thread::current().id()));
Some(code)
}
fn stalled(&self, wait_operations: u32, milliseconds: u32) -> Option<(HRESULT, Duration)> {
let mut state = lock(&self.shared);
let (since, length) = match state.stall {
Stall::Off => return None,
Stall::For(length) => {
let now = Instant::now();
state.stall = Stall::Since(now, length);
(now, length)
}
Stall::Since(since, length) => (since, length),
};
let left = length.saturating_sub(since.elapsed());
if left.is_zero() {
state.stall = Stall::Off;
return None;
}
Some(if wait_operations == 0 {
(S_OK, Duration::ZERO)
} else {
(
IORING_E_WAIT_TIMEOUT,
left.min(Duration::from_millis(u64::from(milliseconds))),
)
})
}
}
impl Responses for CallFaults {
unsafe fn submit(
&mut self,
ring: *mut c_void,
wait_operations: u32,
milliseconds: u32,
submitted: *mut u32,
) -> HRESULT {
if let Some(code) = self.fails(Call::Submit) {
return code;
}
if let Some((answer, wait)) = self.stalled(wait_operations, milliseconds) {
std::thread::sleep(wait);
if answer == S_OK && !submitted.is_null() {
unsafe { submitted.write(0) };
}
return answer;
}
unsafe { Forward.submit(ring, wait_operations, milliseconds, submitted) }
}
unsafe fn pop(&mut self, ring: *mut c_void, cqe: *mut IORING_CQE) -> HRESULT {
if let Some(code) = self.fails(Call::Pop) {
return code;
}
let answer = unsafe { Forward.pop(ring, cqe) };
if answer == S_OK
&& let Some(code) = Fail::spend(&mut lock(&self.shared).completions)
{
unsafe { (*cqe).ResultCode = code };
}
answer
}
#[allow(clippy::too_many_arguments)]
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 {
if let Some(code) = self.fails(Call::BuildRead) {
return code;
}
unsafe { Forward.build_read(ring, file, buffer, bytes, offset, user_data, flags) }
}
#[allow(clippy::too_many_arguments)]
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 {
if let Some(code) = self.fails(Call::BuildWrite) {
return code;
}
unsafe { Forward.build_write(ring, file, buffer, bytes, offset, caching, user_data, flags) }
}
unsafe fn build_flush(
&mut self,
ring: *mut c_void,
file: IORING_HANDLE_REF,
mode: FILE_FLUSH_MODE,
user_data: usize,
flags: i32,
) -> HRESULT {
if let Some(code) = self.fails(Call::BuildFlush) {
return code;
}
unsafe { Forward.build_flush(ring, file, mode, user_data, flags) }
}
unsafe fn build_cancel(
&mut self,
ring: *mut c_void,
file: IORING_HANDLE_REF,
cancel_user_data: usize,
user_data: usize,
) -> HRESULT {
if let Some(code) = self.fails(Call::BuildCancel) {
return code;
}
unsafe { Forward.build_cancel(ring, file, cancel_user_data, user_data) }
}
unsafe fn build_register_files(
&mut self,
ring: *mut c_void,
count: u32,
handles: *const *mut c_void,
user_data: usize,
) -> HRESULT {
if let Some(code) = self.fails(Call::BuildRegisterFiles) {
return code;
}
unsafe { Forward.build_register_files(ring, count, handles, user_data) }
}
unsafe fn build_register_buffers(
&mut self,
ring: *mut c_void,
count: u32,
buffers: *const IORING_BUFFER_INFO,
user_data: usize,
) -> HRESULT {
if let Some(code) = self.fails(Call::BuildRegisterBuffers) {
return code;
}
unsafe { Forward.build_register_buffers(ring, count, buffers, user_data) }
}
unsafe fn set_completion_event(&mut self, ring: *mut c_void, event: *mut c_void) -> HRESULT {
if let Some(code) = self.fails(Call::SetCompletionEvent) {
return code;
}
unsafe { Forward.set_completion_event(ring, event) }
}
}
#[must_use]
pub fn submit_faults() -> (CallFaults, SubmitFaultControl) {
let (faults, control) = call_faults();
(faults, SubmitFaultControl { control })
}
#[derive(Debug, Clone)]
pub struct SubmitFaultControl {
control: CallFaultControl,
}
impl SubmitFaultControl {
pub fn fail_next(&self, count: u32, failure: InjectedFailure) {
self.control.fail_next(Call::Submit, count, failure);
}
pub fn fail_until_cleared(&self, failure: InjectedFailure) {
self.control.fail_until_cleared(Call::Submit, failure);
}
pub fn clear(&self) {
self.control.clear();
}
#[must_use]
pub fn failures(&self) -> Vec<ThreadId> {
self.control.failures(Call::Submit)
}
}
#[cfg(test)]
mod tests;