use std::time::{Duration, Instant};
use windows_ioring_sys::InjectedFailure;
use windows_ioring_sys::sys::Call;
use super::gates::expect_durable;
use super::pumped::{next as pumped_next, pumped, readiness_set};
use super::pushes::{BOUND, GIVEN, Harness, RingEntry, given, instance, temp};
use durable_ioring::DurableRing;
use durable_ioring::backend::{RefusalCause, SubmissionData};
use durable_ioring::{DurabilityRequest, Entry, OpKind, ReadOptions};
fn device() -> InjectedFailure {
InjectedFailure::Win32(1117)
}
fn cause_of(error: &std::io::Error) -> &RefusalCause {
error
.get_ref()
.and_then(|source| source.downcast_ref::<RefusalCause>())
.expect("a pop's failed submit is reported as its RefusalCause")
}
fn is_the_armed_failure(cause: &RefusalCause) -> bool {
matches!(
cause,
RefusalCause::Unknown(fault) if fault.code.and_then(crate::win32_code_of) == Some(1117)
)
}
fn pop_through_failures(harness: &mut Harness) -> RingEntry {
let started = Instant::now();
loop {
if let Ok(Some(entry)) = harness.ring.pop() {
harness
.oracle
.observe(&entry)
.expect("the oracle accepts the entry");
return entry;
}
assert!(
started.elapsed() < BOUND,
"no entry within {BOUND:?}; {} outstanding",
harness.oracle.outstanding()
);
std::thread::sleep(Duration::from_millis(1));
}
}
#[test]
fn a_pop_whose_submit_fails_reports_its_cause_and_the_work_is_issued_once_submits_succeed() {
let temp = temp(&[0; 64]);
let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new()));
let faults = harness.ring.engine().backend.submit_faults();
faults.fail_until_cleared(device());
let id = harness.write(GIVEN, 0, b"queued", 1, 7);
let error = harness
.ring
.pop()
.expect_err("nothing to return, and the submit that is owed fails");
assert!(
is_the_armed_failure(cause_of(&error)),
"reported with its cause: {error}"
);
let again = harness.ring.pop().expect_err("a report is not consumed");
assert!(is_the_armed_failure(cause_of(&again)));
faults.clear();
let done = harness.next();
assert_eq!(done.id, id, "the work was issued, and completed");
assert_eq!(done.context, 7);
assert_eq!(super::pushes::transferred(&done), 6);
assert_eq!(
std::fs::read(&temp.0).expect("read the file")[..6],
*b"queued",
"and it reached the file"
);
harness.finish();
}
#[test]
fn a_drain_whose_submit_fails_on_a_pool_thread_leaves_the_work_queued_for_the_next_submit() {
let temp = temp(&[7; 64]);
let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new()));
let faults = harness.ring.engine().backend.submit_faults();
let here = std::thread::current().id();
let (first, second) = {
let mut engine = harness.ring.engine();
engine.backend.enable_owned_reads();
let first = engine
.read(
GIVEN,
0,
SubmissionData::Owned(vec![0; 4]),
1,
ReadOptions::new(),
)
.expect("accepted");
faults.fail_until_cleared(device());
let second = engine
.read(
GIVEN,
4,
SubmissionData::Owned(vec![0; 4]),
2,
ReadOptions::new(),
)
.expect("accepted, though its own submit fails");
(first, second)
};
harness.pushed(first, OpKind::Read, 4, 1);
harness.pushed(second, OpKind::Read, 4, 2);
let Entry::Op(done) = pop_through_failures(&mut harness) else {
panic!("the first read's completion");
};
assert_eq!(done.id, first);
let threads = faults.failures();
assert!(
threads.iter().any(|thread| *thread != here),
"a submit failed on a thread other than the test's: {threads:?}"
);
let started = Instant::now();
while started.elapsed() < Duration::from_millis(100) {
let error = harness
.ring
.pop()
.expect_err("nothing to return, and a submit is owed");
assert!(is_the_armed_failure(cause_of(&error)));
std::thread::sleep(Duration::from_millis(1));
}
faults.clear();
let done = harness.next();
assert_eq!(done.id, second, "the next submit issued it");
assert_eq!(done.context, 2);
harness.finish();
}
#[test]
fn a_seal_whose_commit_submit_fails_is_made_durable_once_submits_succeed() {
let temp = temp(&[0; 64]);
let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new()));
let faults = harness.ring.engine().backend.submit_faults();
harness.write(GIVEN, 0, b"log", 1, 1);
harness.next();
faults.fail_until_cleared(device());
assert_eq!(harness.seal(1), DurabilityRequest::Submitted);
let error = harness
.ring
.pop()
.expect_err("the commit is queued, not issued, and its submit fails");
assert!(is_the_armed_failure(cause_of(&error)));
assert_eq!(
harness.durable(),
None,
"not durable while the commit has not run"
);
faults.clear();
let one = harness.epoch(1);
expect_durable(&mut harness, one);
assert_eq!(harness.durable(), Some(1));
harness.finish();
}
fn fault_of(error: &std::io::Error) -> &crate::IoFault {
error
.get_ref()
.and_then(|source| source.downcast_ref::<crate::IoFault>())
.expect("a pop's unreadable ring is reported as its IoFault")
}
const SETTLED: Duration = Duration::from_millis(250);
#[test]
fn a_write_whose_doorbell_fails_sets_the_readiness_signal_once_and_the_pop_reports_the_cause() {
let temp = temp(&[0; 64]);
let mut harness = pumped(vec![given(GIVEN, &temp)]);
let faults = harness.ring.engine().backend.submit_faults();
assert!(!readiness_set(&mut harness), "nothing has set it");
faults.fail_until_cleared(device());
let id = harness.write(GIVEN, 0, b"queued", 1, 7);
assert!(
readiness_set(&mut harness),
"the failed doorbell told the consumer"
);
assert!(!readiness_set(&mut harness), "once");
let error = harness.ring.pop().expect_err("the doorbell fails again");
assert!(is_the_armed_failure(cause_of(&error)));
faults.clear();
assert!(
harness.ring.pop().expect("the doorbell works").is_none(),
"the pop's own retry issued it, and nothing has completed yet"
);
let Entry::Op(done) = pumped_next(&mut harness) else {
panic!("the write's completion");
};
assert_eq!(done.id, id);
harness.finish();
}
#[test]
fn a_ring_that_cannot_be_read_sets_the_readiness_signal_and_the_pop_reports_it_once() {
let temp = temp(&[0; 64]);
let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new()));
let faults = harness.ring.engine().backend.call_faults();
faults.fail_until_cleared(Call::Pop, device());
harness.write(GIVEN, 0, b"queued", 1, 7);
std::thread::sleep(SETTLED);
assert!(
readiness_set(&mut harness),
"the drain that could not read told the consumer"
);
let error = harness.ring.pop().expect_err("nothing to return, and why");
assert_eq!(
fault_of(&error).code.and_then(crate::win32_code_of),
Some(1117)
);
assert!(
harness.ring.pop().expect("nothing more to say").is_none(),
"reported once"
);
faults.clear();
}
#[test]
fn a_pumped_instance_whose_ring_could_not_be_read_reads_it_at_the_next_pump() {
let temp = temp(&[0; 64]);
let mut harness = pumped(vec![given(GIVEN, &temp)]);
let faults = harness.ring.engine().backend.call_faults();
let id = harness.write(GIVEN, 0, b"queued", 1, 7);
std::thread::sleep(SETTLED);
faults.fail_next(Call::Pop, 1, device());
assert!(
harness.ring.pump(Some(BOUND)).expect("pump"),
"told, so the consumer pops"
);
let error = harness
.ring
.pop()
.expect_err("the unreadable ring, reported");
assert_eq!(
fault_of(&error).code.and_then(crate::win32_code_of),
Some(1117)
);
let Entry::Op(done) = pumped_next(&mut harness) else {
panic!("the write's completion");
};
assert_eq!(done.id, id, "read once the ring could be");
harness.finish();
}
#[test]
fn a_fault_the_next_read_got_past_is_not_reported_to_the_consumer() {
let temp = temp(&[0; 64]);
let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new()));
let faults = harness.ring.engine().backend.call_faults();
faults.fail_next(Call::Pop, 1, device());
let id = harness.write(GIVEN, 0, b"queued", 1, 7);
std::thread::sleep(SETTLED);
let done = harness.next();
assert_eq!(done.id, id);
assert!(
harness.ring.pop().expect("no stale report").is_none(),
"nothing left, and no fault"
);
harness.finish();
}