use std::io;
use std::os::windows::io::{AsHandle, AsRawHandle};
use std::ptr;
use std::time::Duration;
use windows_sys::Win32::Foundation::{
FALSE, HANDLE, TRUE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_IO_COMPLETION, WAIT_OBJECT_0,
WAIT_TIMEOUT,
};
use windows_sys::Win32::System::SystemServices::MAXIMUM_WAIT_OBJECTS;
use windows_sys::Win32::System::Threading::WaitForMultipleObjectsEx;
use crate::wait::{Alertable, WaitOutcome, milliseconds};
pub const MAXIMUM_OBJECTS: usize = MAXIMUM_WAIT_OBJECTS as usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WaitAnyOutcome {
Signalled(usize),
Abandoned(usize),
TimedOut,
Alerted,
}
pub fn wait_for_any(
objects: &[impl AsHandle],
timeout: Option<Duration>,
alertable: Alertable,
) -> io::Result<WaitAnyOutcome> {
let (code, count) = wait(objects, false, timeout, alertable)?;
any_outcome(code, count)
}
pub fn wait_for_all(
objects: &[impl AsHandle],
timeout: Option<Duration>,
alertable: Alertable,
) -> io::Result<WaitOutcome> {
let (code, count) = wait(objects, true, timeout, alertable)?;
all_outcome(code, count)
}
fn wait(
objects: &[impl AsHandle],
wait_all: bool,
timeout: Option<Duration>,
alertable: Alertable,
) -> io::Result<(u32, usize)> {
let milliseconds = milliseconds(timeout)?;
let count = objects.len();
if count == 0 || count > MAXIMUM_OBJECTS {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("a wait takes from 1 to {MAXIMUM_OBJECTS} objects, and was given {count}"),
));
}
let mut handles: [HANDLE; MAXIMUM_OBJECTS] = [ptr::null_mut(); MAXIMUM_OBJECTS];
for (slot, object) in handles.iter_mut().zip(objects) {
*slot = object.as_handle().as_raw_handle();
}
let code = unsafe {
WaitForMultipleObjectsEx(
u32::try_from(count).unwrap_or(MAXIMUM_WAIT_OBJECTS),
handles.as_ptr(),
if wait_all { TRUE } else { FALSE },
milliseconds,
alertable.as_bool(),
)
};
Ok((code, count))
}
fn any_outcome(code: u32, count: usize) -> io::Result<WaitAnyOutcome> {
let signalled = |first: u32| {
code.checked_sub(first)
.and_then(|index| usize::try_from(index).ok())
.filter(|&index| index < count)
};
if let Some(index) = signalled(WAIT_OBJECT_0) {
return Ok(WaitAnyOutcome::Signalled(index));
}
if let Some(index) = signalled(WAIT_ABANDONED_0) {
return Ok(WaitAnyOutcome::Abandoned(index));
}
match code {
WAIT_TIMEOUT => Ok(WaitAnyOutcome::TimedOut),
WAIT_IO_COMPLETION => Ok(WaitAnyOutcome::Alerted),
WAIT_FAILED => Err(io::Error::last_os_error()),
other => Err(io::Error::other(format!(
"a wait for any of {count} objects returned {other:#x}"
))),
}
}
fn all_outcome(code: u32, count: usize) -> io::Result<WaitOutcome> {
let within =
|first: u32| code >= first && usize::try_from(code - first).is_ok_and(|n| n < count);
if within(WAIT_OBJECT_0) {
return Ok(WaitOutcome::Signalled);
}
if within(WAIT_ABANDONED_0) {
return Ok(WaitOutcome::Abandoned);
}
match code {
WAIT_TIMEOUT => Ok(WaitOutcome::TimedOut),
WAIT_IO_COMPLETION => Ok(WaitOutcome::Alerted),
WAIT_FAILED => Err(io::Error::last_os_error()),
other => Err(io::Error::other(format!(
"a wait for all of {count} objects returned {other:#x}"
))),
}
}
#[cfg(test)]
mod tests;