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;