win_sync_sys/wait.rs
1// Copyright (c) 2026 Mike Grier
2//! Waiting on one kernel object: `WaitForSingleObject` and `WaitForSingleObjectEx`.
3
4use std::io;
5use std::os::windows::io::{AsHandle, AsRawHandle};
6use std::time::Duration;
7
8use windows_sys::Win32::Foundation::{
9 FALSE, TRUE, WAIT_ABANDONED, WAIT_FAILED, WAIT_IO_COMPLETION, WAIT_OBJECT_0, WAIT_TIMEOUT,
10};
11use windows_sys::Win32::System::Threading::{INFINITE, WaitForSingleObject, WaitForSingleObjectEx};
12
13/// Whether a wait can be ended early by an APC queued to the waiting thread.
14///
15/// An alertable wait runs every APC queued to the thread, and I/O completion routines, before it
16/// returns, and then reports [`WaitOutcome::Alerted`]; a wait that is not alertable leaves them
17/// queued. [`queue_user_apc`](crate::queue_user_apc) is what queues one.
18///
19/// **A pending APC wins over a signalled object.** On the Windows this was observed on, an
20/// alertable wait with an APC already queued runs it and returns `Alerted` even when the object is
21/// signalled, and leaves the object as it was; the next wait takes it.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum Alertable {
24 /// The wait ends only by its object, or its timeout.
25 No,
26 /// A queued APC ends the wait.
27 Yes,
28}
29
30impl Alertable {
31 /// The `BOOL` the Win32 calls take.
32 pub(crate) fn as_bool(self) -> i32 {
33 match self {
34 Self::No => FALSE,
35 Self::Yes => TRUE,
36 }
37 }
38}
39
40/// How a wait that did not fail ended.
41///
42/// Non-exhaustive, so a wait that can end another way can be added.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44#[non_exhaustive]
45pub enum WaitOutcome {
46 /// The object was signalled, and the wait has taken whatever a signalled object gives: an
47 /// auto-reset event is unsignalled again, a mutex is owned by the waiting thread, a semaphore's
48 /// count is one lower.
49 Signalled,
50 /// The object is a mutex whose owner ended without releasing it. The waiting thread owns it
51 /// now, and whatever it protected may be inconsistent. No other kind of object ends a wait
52 /// this way, but the wait is over any handle, so a caller that cannot rule the handle out
53 /// cannot ignore it.
54 Abandoned,
55 /// The timeout passed with the object not signalled.
56 TimedOut,
57 /// An alertable wait was ended by an APC queued to the thread, which has run. Only a wait made
58 /// [`Alertable::Yes`] ends this way. **The object was not waited for**: nothing was taken from
59 /// it, so the caller waits again if it still needs it.
60 Alerted,
61}
62
63/// Wait for `object` to be signalled, for up to `timeout`: `None` is no limit, and `Duration::ZERO`
64/// only asks whether it is signalled now.
65///
66/// **A satisfied wait changes the object**, as the Win32 call does: it resets an auto-reset event,
67/// acquires a mutex and decrements a semaphore. The wait is not alertable, so a queued APC does
68/// not end it; [`wait_for_single_object_ex`] is the one an APC can end.
69///
70/// A timeout is rounded **up** to whole milliseconds, so a wait for a nonzero time is never a poll.
71///
72/// # Errors
73///
74/// [`io::ErrorKind::InvalidInput`] for a timeout the call cannot express: `INFINITE` is `u32::MAX`
75/// milliseconds, so the longest finite wait is one millisecond less, about 49.7 days. It is refused
76/// rather than shortened, since a wait that ends early has given a wrong answer. Otherwise the
77/// error from `GetLastError` when the wait fails, such as access denied for a handle without
78/// `SYNCHRONIZE` access.
79pub fn wait_for_single_object(
80 object: impl AsHandle,
81 timeout: Option<Duration>,
82) -> io::Result<WaitOutcome> {
83 let milliseconds = milliseconds(timeout)?;
84 // SAFETY: a borrowed handle is open for the borrow's lifetime, and the wait ends within it.
85 let code = unsafe { WaitForSingleObject(object.as_handle().as_raw_handle(), milliseconds) };
86 outcome(code)
87}
88
89/// [`wait_for_single_object`], which an APC can end: `WaitForSingleObjectEx`.
90///
91/// With [`Alertable::No`] it is the same wait as [`wait_for_single_object`]. With
92/// [`Alertable::Yes`] it returns [`WaitOutcome::Alerted`] when an APC queued to the calling thread
93/// ran, without having taken anything from the object.
94///
95/// # Errors
96///
97/// As [`wait_for_single_object`].
98pub fn wait_for_single_object_ex(
99 object: impl AsHandle,
100 timeout: Option<Duration>,
101 alertable: Alertable,
102) -> io::Result<WaitOutcome> {
103 let milliseconds = milliseconds(timeout)?;
104 // SAFETY: a borrowed handle is open for the borrow's lifetime, and the wait ends within it.
105 let code = unsafe {
106 WaitForSingleObjectEx(
107 object.as_handle().as_raw_handle(),
108 milliseconds,
109 alertable.as_bool(),
110 )
111 };
112 outcome(code)
113}
114
115/// A timeout as the call takes it: whole milliseconds rounded up, `INFINITE` for none.
116pub(crate) fn milliseconds(timeout: Option<Duration>) -> io::Result<u32> {
117 let Some(timeout) = timeout else {
118 return Ok(INFINITE);
119 };
120 let rounded_up = timeout.as_nanos().div_ceil(1_000_000);
121 match u32::try_from(rounded_up) {
122 // `INFINITE` is a value of the same parameter, so a finite wait may not take it.
123 Ok(milliseconds) if milliseconds != INFINITE => Ok(milliseconds),
124 _ => Err(io::Error::new(
125 io::ErrorKind::InvalidInput,
126 "a wait of this many milliseconds cannot be expressed: the longest finite wait is \
127 u32::MAX - 1",
128 )),
129 }
130}
131
132/// What a wait on one object returned, as an outcome or an error.
133pub(crate) fn outcome(code: u32) -> io::Result<WaitOutcome> {
134 match code {
135 WAIT_OBJECT_0 => Ok(WaitOutcome::Signalled),
136 WAIT_ABANDONED => Ok(WaitOutcome::Abandoned),
137 WAIT_TIMEOUT => Ok(WaitOutcome::TimedOut),
138 WAIT_IO_COMPLETION => Ok(WaitOutcome::Alerted),
139 WAIT_FAILED => Err(io::Error::last_os_error()),
140 // The call returns no other value; named so the next reader does not have to wonder, and
141 // not assumed away.
142 other => Err(io::Error::other(format!("a wait returned {other:#x}"))),
143 }
144}
145
146#[cfg(test)]
147mod tests;