#![cfg(windows)]
use std::fs::File;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use windows_ioring_sys::{Batch, IoRing, PushOptions, capabilities};
use windows_sys::Win32::Foundation::{HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT};
use windows_sys::Win32::System::Threading::{
CreateEventW, SetEvent, WaitForMultipleObjects, WaitForSingleObject,
};
const POP_BOUND: std::time::Duration = std::time::Duration::from_secs(10);
const CHUNKS: usize = 8;
const CHUNK_LEN: usize = 512;
const SIGNAL_TIMEOUT_MS: u32 = 5_000;
const LOST_WAKEUP_MS: u32 = 500;
const MAX_DRAIN_ATTEMPTS: usize = 512;
type EventRing = IoRing<Vec<u8>>;
mod common;
fn temp_file(tag: &str) -> common::TempPath {
common::TempPath::new("completion-event", tag)
}
fn fixture(tag: &str) -> File {
let path = temp_file(tag);
let mut content = vec![0_u8; CHUNKS * CHUNK_LEN];
for (chunk_index, chunk) in content.chunks_mut(CHUNK_LEN).enumerate() {
chunk.fill(chunk_index as u8);
}
std::fs::write(&path, &content).expect("write fixture file");
std::fs::OpenOptions::new()
.read(true)
.open(&path)
.expect("open for read")
}
fn unrelated_event() -> OwnedHandle {
let raw = unsafe { CreateEventW(std::ptr::null(), 0, 0, std::ptr::null()) };
assert!(!raw.is_null(), "CreateEventW failed");
unsafe { OwnedHandle::from_raw_handle(raw) }
}
fn signal(event: &OwnedHandle) {
assert!(
unsafe { SetEvent(event.as_raw_handle()) } != 0,
"SetEvent failed"
);
}
fn signalled_within(event: &OwnedHandle, timeout_ms: u32) -> bool {
let result = unsafe { WaitForSingleObject(event.as_raw_handle(), timeout_ms) };
if result == WAIT_OBJECT_0 {
true
} else if result == WAIT_TIMEOUT {
false
} else {
panic!("unexpected WaitForSingleObject result 0x{result:08X}");
}
}
fn signalled_now(event: &OwnedHandle) -> bool {
signalled_within(event, 0)
}
fn wait_either(first: &OwnedHandle, second: &OwnedHandle, timeout_ms: u32) -> Option<usize> {
let handles: [HANDLE; 2] = [first.as_raw_handle(), second.as_raw_handle()];
let result = unsafe { WaitForMultipleObjects(2, handles.as_ptr(), 0, timeout_ms) };
if result == WAIT_OBJECT_0 {
Some(0)
} else if result == WAIT_OBJECT_0 + 1 {
Some(1)
} else if result == WAIT_TIMEOUT {
None
} else {
panic!("unexpected WaitForMultipleObjects result 0x{result:08X}");
}
}
fn submit_reads(ring: &mut EventRing, file: &File, count: usize, wait_operations: u32) {
assert!(count <= CHUNKS, "the fixture only has {CHUNKS} chunks");
let handle = file.as_raw_handle();
let mut batch = Batch::new(ring);
for chunk_index in 0..count {
let buffer = vec![0_u8; CHUNK_LEN];
let offset = (chunk_index * CHUNK_LEN) as u64;
unsafe { batch.read_raw_owned(handle, buffer, (), offset, PushOptions::new()) }
.expect("queue read");
}
batch
.submit_and_wait(wait_operations, SIGNAL_TIMEOUT_MS)
.expect("submit");
}
fn drain_to_empty(ring: &mut EventRing) -> usize {
let mut popped = 0;
while let Some((completion, held)) = ring.try_pop().expect("pop completion") {
completion.result().expect("read succeeded");
let _buffer = held.expect("the ring was holding this read's buffer");
popped += 1;
}
popped
}
fn drain_exactly(ring: &mut EventRing, want: usize) {
let mut popped = 0;
let mut attempts = 0;
while popped < want {
attempts += 1;
assert!(
attempts <= MAX_DRAIN_ATTEMPTS,
"only {popped} of {want} completions ever arrived"
);
popped += drain_to_empty(ring);
}
assert_eq!(popped, want, "more completions arrived than were submitted");
}
fn ring_with_event(submission: u32, completion: u32) -> (EventRing, OwnedHandle) {
let mut ring = EventRing::with_inventory(submission, completion).expect("create ring");
let event = ring.completion_event().expect(
"this host must report IORING_FEATURE_SET_COMPLETION_EVENT to run the M11.2 contract tests",
);
(ring, event)
}
fn settle(ring: &mut EventRing, event: &OwnedHandle) {
assert!(
signalled_within(event, SIGNAL_TIMEOUT_MS),
"attaching must signal once (rule 3)"
);
drain_to_empty(ring);
assert!(
!signalled_now(event),
"the setup signal must be consumable exactly once"
);
}
#[test]
fn attaching_to_a_fresh_ring_signals_once_and_leaves_nothing_to_pop() {
let (mut ring, event) = ring_with_event(64, 64);
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"attaching must signal once even with no work outstanding (rule 3)"
);
assert_eq!(
drain_to_empty(&mut ring),
0,
"a wake with nothing to pop is normal, not an error (rule 3)"
);
assert!(
!signalled_now(&event),
"the setup signal is one edge, not a level held high"
);
}
#[test]
fn attaching_to_a_ring_whose_queue_is_already_non_empty_still_signals() {
let file = fixture("backlog");
let mut ring = EventRing::with_inventory(64, 64).expect("create ring");
submit_reads(&mut ring, &file, CHUNKS, CHUNKS as u32);
let event = ring.completion_event().expect("completion event");
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"a caller that submitted before attaching must still be woken for its backlog"
);
drain_exactly(&mut ring, CHUNKS);
}
#[test]
fn a_repeat_call_hands_back_a_duplicate_of_the_same_event() {
let (mut ring, first) = ring_with_event(64, 64);
let second = ring.completion_event().expect("repeat completion event");
assert!(
signalled_within(&first, SIGNAL_TIMEOUT_MS),
"the setup signal must be visible through the first duplicate"
);
assert!(
!signalled_now(&second),
"a repeat call must return a duplicate of the same event, not attach a second one"
);
}
#[test]
fn a_repeat_call_signals_again_once_the_earlier_signal_has_been_consumed() {
let file = fixture("repeat-after-consume");
let (mut ring, first) = ring_with_event(64, 64);
assert!(
signalled_within(&first, SIGNAL_TIMEOUT_MS),
"attaching raises the setup signal (rule 3)"
);
submit_reads(&mut ring, &file, CHUNKS, CHUNKS as u32);
assert!(
signalled_within(&first, SIGNAL_TIMEOUT_MS),
"the completions take the queue from empty to non-empty, which signals once (rule 1)"
);
let second = ring.completion_event().expect("repeat completion event");
assert!(
signalled_within(&second, SIGNAL_TIMEOUT_MS),
"a repeat call must raise its own setup signal; without it a waiter starting here \
never learns about the backlog already in the queue"
);
drain_exactly(&mut ring, CHUNKS);
}
#[test]
fn the_ring_still_signals_both_duplicates_after_a_repeat_call() {
let file = fixture("idempotent");
let (mut ring, first) = ring_with_event(64, 64);
let second = ring.completion_event().expect("repeat completion event");
settle(&mut ring, &first);
submit_reads(&mut ring, &file, 1, 1);
assert!(
signalled_within(&first, SIGNAL_TIMEOUT_MS),
"a repeat call must not detach the event the first call handed out"
);
assert!(
!signalled_now(&second),
"both handles name one event, so one completion edge satisfies exactly one wait"
);
drain_exactly(&mut ring, 1);
submit_reads(&mut ring, &file, 1, 1);
assert!(
signalled_within(&second, SIGNAL_TIMEOUT_MS),
"the handle from the repeat call names that same event"
);
assert!(
!signalled_now(&first),
"one edge cannot satisfy a wait on both handles if they are one event"
);
drain_exactly(&mut ring, 1);
}
#[test]
fn closing_one_duplicate_does_not_stop_the_ring_signalling_the_other() {
let file = fixture("closed-duplicate");
let (mut ring, first) = ring_with_event(64, 64);
let second = ring.completion_event().expect("repeat completion event");
settle(&mut ring, &first);
drop(second);
submit_reads(&mut ring, &file, 1, 1);
assert!(
signalled_within(&first, SIGNAL_TIMEOUT_MS),
"closing a duplicate must not disturb the ring's own event"
);
drain_exactly(&mut ring, 1);
}
#[test]
fn the_returned_handle_stays_valid_after_the_ring_is_dropped() {
let (mut ring, event) = ring_with_event(8, 8);
settle(&mut ring, &event);
drop(ring);
assert!(
!signalled_now(&event),
"an outlived duplicate must remain a valid, waitable, unsignalled handle"
);
}
#[test]
fn a_completion_arriving_into_an_empty_queue_signals() {
let file = fixture("edge");
let (mut ring, event) = ring_with_event(64, 64);
settle(&mut ring, &event);
submit_reads(&mut ring, &file, 1, 1);
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"empty -> non-empty is the edge the event reports (rule 1)"
);
drain_exactly(&mut ring, 1);
}
#[test]
fn many_completions_arriving_at_once_produce_exactly_one_wakeup() {
let file = fixture("batch");
let (mut ring, event) = ring_with_event(64, 64);
settle(&mut ring, &event);
submit_reads(&mut ring, &file, CHUNKS, CHUNKS as u32);
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"the first completion into an empty queue must signal"
);
assert!(
!signalled_now(&event),
"completions arriving into an already non-empty queue must not signal again"
);
assert_eq!(
drain_to_empty(&mut ring),
CHUNKS,
"one drain-to-empty must retrieve every completion the single wakeup covered"
);
}
#[test]
fn the_edge_re_arms_after_every_drain_to_empty() {
let file = fixture("re-arm");
let (mut ring, event) = ring_with_event(64, 64);
settle(&mut ring, &event);
for round in 0..3 {
submit_reads(&mut ring, &file, 1, 1);
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"round {round}: the edge must re-arm after the previous drain"
);
drain_exactly(&mut ring, 1);
assert!(
!signalled_now(&event),
"round {round}: a full drain must leave no leftover signal"
);
}
}
#[test]
fn the_ring_still_wakes_after_an_unrelated_handle_fires_in_a_multiplexed_wait() {
let file = fixture("multiplexed");
let (mut ring, event) = ring_with_event(64, 64);
let other = unrelated_event();
settle(&mut ring, &event);
signal(&other);
assert_eq!(
wait_either(&event, &other, SIGNAL_TIMEOUT_MS),
Some(1),
"round 0: the unrelated handle must wake the multiplexed wait"
);
assert_eq!(
drain_to_empty(&mut ring),
0,
"round 0: a wake with nothing to pop is normal (rule 3)"
);
submit_reads(&mut ring, &file, CHUNKS, CHUNKS as u32);
assert_eq!(
wait_either(&event, &other, SIGNAL_TIMEOUT_MS),
Some(0),
"round 1: the ring must wake the wait after the unrelated handle already did"
);
assert_eq!(
drain_to_empty(&mut ring),
CHUNKS,
"round 1: one drain-to-empty must retrieve the whole batch the single wakeup covered"
);
signal(&other);
assert_eq!(
wait_either(&event, &other, SIGNAL_TIMEOUT_MS),
Some(1),
"round 2: the unrelated handle must still wake the wait"
);
assert_eq!(
drain_to_empty(&mut ring),
0,
"round 2: rule 2 says drain on every pass, not only the ring's"
);
submit_reads(&mut ring, &file, 1, 1);
assert_eq!(
wait_either(&event, &other, SIGNAL_TIMEOUT_MS),
Some(0),
"round 3: the ring must still wake the wait after two unrelated wakeups"
);
assert_eq!(
drain_to_empty(&mut ring),
1,
"round 3: the final completion must be retrievable"
);
submit_reads(&mut ring, &file, CHUNKS, CHUNKS as u32);
assert_eq!(
wait_either(&event, &other, SIGNAL_TIMEOUT_MS),
Some(0),
"round 4: the batch's first completion must wake the wait"
);
let (_stranded, held) = ring
.pop_within(POP_BOUND)
.expect("pop one")
.expect("the batch's remaining completion arrives within the bound");
let _buffer = held.expect("the ring was holding this read's buffer");
submit_reads(&mut ring, &file, 1, 1);
assert_eq!(
wait_either(&event, &other, LOST_WAKEUP_MS),
None,
"a completion arriving into a queue that never returned to empty must not signal (rule 1)"
);
assert_eq!(
drain_to_empty(&mut ring),
CHUNKS,
"the stranded entries must still be poppable once the waiter drains"
);
assert_eq!(
ring.held(),
0,
"the ring must be holding nothing once every completion is drained"
);
assert!(
!signalled_now(&event),
"the ring must end quiet, with no leftover signal after a full drain"
);
}
#[test]
fn completion_event_reports_unsupported_exactly_when_the_capability_is_absent() {
let supported = capabilities()
.expect("capabilities")
.supports_completion_event;
let mut ring = EventRing::with_inventory(8, 8).expect("create ring");
match ring.completion_event() {
Ok(event) => {
assert!(
supported,
"completion_event succeeded on a host that does not report the capability"
);
assert!(
signalled_within(&event, SIGNAL_TIMEOUT_MS),
"a successful attach owes the caller its setup signal"
);
}
Err(error) => {
assert!(
!supported,
"completion_event failed on a host that reports the capability: {error}"
);
assert_eq!(
error.kind(),
std::io::ErrorKind::Unsupported,
"a missing capability must be reported as Unsupported"
);
}
}
}