Skip to main content

win_sync_sys/
wait_many.rs

1// Copyright (c) 2026 Mike Grier
2//! Waiting on several kernel objects at once: `WaitForMultipleObjectsEx`.
3
4use std::io;
5use std::os::windows::io::{AsHandle, AsRawHandle};
6use std::ptr;
7use std::time::Duration;
8
9use windows_sys::Win32::Foundation::{
10    FALSE, HANDLE, TRUE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_IO_COMPLETION, WAIT_OBJECT_0,
11    WAIT_TIMEOUT,
12};
13use windows_sys::Win32::System::SystemServices::MAXIMUM_WAIT_OBJECTS;
14use windows_sys::Win32::System::Threading::WaitForMultipleObjectsEx;
15
16use crate::wait::{Alertable, WaitOutcome, milliseconds};
17
18/// The most objects one wait takes: `MAXIMUM_WAIT_OBJECTS`.
19///
20/// More is refused, not split into several waits, since a wait on the first sixty-four is not a wait
21/// on all of them; wait on groups of at most this many from different threads, or wait on a
22/// smaller set that is signalled from the rest.
23pub const MAXIMUM_OBJECTS: usize = MAXIMUM_WAIT_OBJECTS as usize;
24
25/// How a [`wait_for_any`] that did not fail ended.
26///
27/// Non-exhaustive, as [`WaitOutcome`] is.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29#[non_exhaustive]
30pub enum WaitAnyOutcome {
31    /// The object at this index was signalled, and the wait has taken what a signalled object gives
32    /// from **that one object only**. Of several signalled at once the lowest index is reported,
33    /// and the others are left as they were: an auto-reset event among them is still signalled.
34    Signalled(usize),
35    /// The object at this index is a mutex whose owner ended without releasing it. The waiting
36    /// thread owns it now, and whatever it protected may be inconsistent. The lowest index of the
37    /// signalled objects is reported here as it is for [`Signalled`](Self::Signalled).
38    Abandoned(usize),
39    /// The timeout passed with none of the objects signalled.
40    TimedOut,
41    /// An alertable wait was ended by an APC queued to the thread, which has run. **Nothing was
42    /// taken from any object.**
43    Alerted,
44}
45
46/// Wait until **any one** of `objects` is signalled, for up to `timeout`: `None` is no limit and
47/// `Duration::ZERO` only asks. `WaitForMultipleObjectsEx` with `bWaitAll` false.
48///
49/// The objects are the borrowed handles of whatever the caller holds: a slice of `&Event`, of
50/// `OwnedHandle`, or of `BorrowedHandle` for a mixture, made with `as_handle`.
51///
52/// **Only the signalled object reported has been changed** by the wait: an auto-reset event is
53/// unsignalled again, a mutex owned, a semaphore's count one lower. The others are as they were,
54/// signalled or not.
55///
56/// # Errors
57///
58/// [`io::ErrorKind::InvalidInput`], without waiting, for no objects, for more than
59/// [`MAXIMUM_OBJECTS`], or for a timeout the call cannot express (see
60/// [`wait_for_single_object`](crate::wait_for_single_object)). Otherwise the error from
61/// `GetLastError` when the wait fails, such as access denied for a handle without `SYNCHRONIZE`
62/// access, or invalid handle.
63pub fn wait_for_any(
64    objects: &[impl AsHandle],
65    timeout: Option<Duration>,
66    alertable: Alertable,
67) -> io::Result<WaitAnyOutcome> {
68    let (code, count) = wait(objects, false, timeout, alertable)?;
69    any_outcome(code, count)
70}
71
72/// Wait until **all** of `objects` are signalled at once, for up to `timeout`: `None` is no limit and
73/// `Duration::ZERO` only asks. `WaitForMultipleObjectsEx` with `bWaitAll` true.
74///
75/// **The wait is atomic**: it takes from the objects only when every one is signalled, and takes
76/// from all of them together. A wait that times out has taken nothing, so an object that was
77/// signalled during it is still signalled. **The same handle may not appear twice**: Windows
78/// refuses it, and the error is the wait's. ([`wait_for_any`] tolerates it and reports the first
79/// index.)
80///
81/// The outcomes are [`WaitOutcome`]'s: `Signalled` is every object signalled and taken from;
82/// `Abandoned` is the same with at least one a mutex whose owner ended without releasing it, and
83/// the waiting thread owns every mutex; `Alerted` is an alertable wait ended by an APC, with nothing
84/// taken.
85///
86/// # Errors
87///
88/// As [`wait_for_any`].
89pub fn wait_for_all(
90    objects: &[impl AsHandle],
91    timeout: Option<Duration>,
92    alertable: Alertable,
93) -> io::Result<WaitOutcome> {
94    let (code, count) = wait(objects, true, timeout, alertable)?;
95    all_outcome(code, count)
96}
97
98/// The call's return value and how many objects it was given.
99fn wait(
100    objects: &[impl AsHandle],
101    wait_all: bool,
102    timeout: Option<Duration>,
103    alertable: Alertable,
104) -> io::Result<(u32, usize)> {
105    let milliseconds = milliseconds(timeout)?;
106    let count = objects.len();
107    if count == 0 || count > MAXIMUM_OBJECTS {
108        return Err(io::Error::new(
109            io::ErrorKind::InvalidInput,
110            format!("a wait takes from 1 to {MAXIMUM_OBJECTS} objects, and was given {count}"),
111        ));
112    }
113    let mut handles: [HANDLE; MAXIMUM_OBJECTS] = [ptr::null_mut(); MAXIMUM_OBJECTS];
114    for (slot, object) in handles.iter_mut().zip(objects) {
115        *slot = object.as_handle().as_raw_handle();
116    }
117    // SAFETY: the first `count` entries are handles borrowed from `objects`, which are open for
118    // the whole call, and `count` is at most the array's length; the call reads only those.
119    let code = unsafe {
120        WaitForMultipleObjectsEx(
121            u32::try_from(count).unwrap_or(MAXIMUM_WAIT_OBJECTS),
122            handles.as_ptr(),
123            if wait_all { TRUE } else { FALSE },
124            milliseconds,
125            alertable.as_bool(),
126        )
127    };
128    Ok((code, count))
129}
130
131/// What a wait for any returned, as an outcome or an error.
132fn any_outcome(code: u32, count: usize) -> io::Result<WaitAnyOutcome> {
133    let signalled = |first: u32| {
134        code.checked_sub(first)
135            .and_then(|index| usize::try_from(index).ok())
136            .filter(|&index| index < count)
137    };
138    if let Some(index) = signalled(WAIT_OBJECT_0) {
139        return Ok(WaitAnyOutcome::Signalled(index));
140    }
141    if let Some(index) = signalled(WAIT_ABANDONED_0) {
142        return Ok(WaitAnyOutcome::Abandoned(index));
143    }
144    match code {
145        WAIT_TIMEOUT => Ok(WaitAnyOutcome::TimedOut),
146        WAIT_IO_COMPLETION => Ok(WaitAnyOutcome::Alerted),
147        WAIT_FAILED => Err(io::Error::last_os_error()),
148        // An index past the objects given, or a value the call does not return.
149        other => Err(io::Error::other(format!(
150            "a wait for any of {count} objects returned {other:#x}"
151        ))),
152    }
153}
154
155/// What a wait for all returned, as an outcome or an error.
156///
157/// For a wait for all the call reports the state of all of the objects, and the index in
158/// `WAIT_OBJECT_0 + n` or `WAIT_ABANDONED_0 + n` is not one a caller can use, so it is not read.
159fn all_outcome(code: u32, count: usize) -> io::Result<WaitOutcome> {
160    let within =
161        |first: u32| code >= first && usize::try_from(code - first).is_ok_and(|n| n < count);
162    if within(WAIT_OBJECT_0) {
163        return Ok(WaitOutcome::Signalled);
164    }
165    if within(WAIT_ABANDONED_0) {
166        return Ok(WaitOutcome::Abandoned);
167    }
168    match code {
169        WAIT_TIMEOUT => Ok(WaitOutcome::TimedOut),
170        WAIT_IO_COMPLETION => Ok(WaitOutcome::Alerted),
171        WAIT_FAILED => Err(io::Error::last_os_error()),
172        other => Err(io::Error::other(format!(
173            "a wait for all of {count} objects returned {other:#x}"
174        ))),
175    }
176}
177
178#[cfg(test)]
179mod tests;