Skip to main content

windows_threadpool_sys/
wait.rs

1// Copyright (c) 2026 Mike Grier
2//! Thread-pool waits: `CreateThreadpoolWait` / `SetThreadpoolWait` /
3//! `WaitForThreadpoolWaitCallbacks` / `CloseThreadpoolWait`.
4//!
5//! A wait object watches one waitable handle and queues its callback when the
6//! handle is signalled or the wait times out. Two SDK contracts shape this API:
7//!
8//! - **The handle must stay valid while a wait is pending.** [`ThreadpoolWait`]
9//!   therefore *owns* its handle rather than borrowing one, so it cannot be
10//!   closed underneath a pending wait. Use [`ThreadpoolWait::handle`] to signal
11//!   or inspect it.
12//! - **A wait fires at most once per arming.** The SDK requires the wait to be
13//!   rearmed explicitly for each activation, so the callback receives a
14//!   [`WaitActivation`] carrying [`WaitActivation::rearm`]. A callback that does
15//!   not rearm simply stops watching.
16//!
17//! Mutex handles are not supported by the thread pool and must not be passed to
18//! [`ThreadpoolWait::new`].
19
20use std::io;
21use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle};
22use std::ptr;
23use std::sync::Mutex;
24use std::sync::atomic::{AtomicIsize, Ordering};
25use std::time::Duration;
26
27use windows_sys::Win32::Foundation::{FALSE, FILETIME, HANDLE, TRUE, WAIT_TIMEOUT};
28use windows_sys::Win32::System::Threading::{
29    CloseThreadpoolWait, CreateEventW, CreateThreadpoolWait, PTP_CALLBACK_INSTANCE, PTP_WAIT,
30    SetThreadpoolWait, WaitForThreadpoolWaitCallbacks,
31};
32use windows_sys::core::BOOL;
33
34use crate::callback_env::CallbackEnviron;
35
36/// Wait results the pool reports. Changing either value is a breaking change.
37mod wait_status {
38    /// `WAIT_OBJECT_0`: the handle was signalled.
39    pub const SIGNALLED: u32 = 0;
40}
41
42/// 100-nanosecond intervals per second, for building a relative `FILETIME`.
43/// Changing this value is a breaking change.
44const FILETIME_TICKS_PER_SECOND: u64 = 10_000_000;
45/// Nanoseconds per 100-nanosecond interval. Changing it is a breaking change.
46const FILETIME_NANOS_PER_TICK: u32 = 100;
47
48/// Build the negative tick count that means "relative timeout".
49fn relative_filetime(timeout: Duration) -> FILETIME {
50    let ticks = timeout
51        .as_secs()
52        .saturating_mul(FILETIME_TICKS_PER_SECOND)
53        .saturating_add(u64::from(timeout.subsec_nanos() / FILETIME_NANOS_PER_TICK));
54    let ticks = i64::try_from(ticks).unwrap_or(i64::MAX);
55    let bits = (-ticks) as u64;
56    FILETIME {
57        dwLowDateTime: bits as u32,
58        dwHighDateTime: (bits >> 32) as u32,
59    }
60}
61
62/// A Win32 routine that closes a wait target.
63///
64/// This is the shape Win32 close routines already have, so one can be passed
65/// directly with no shim: `CloseHandle` and `FindCloseChangeNotification` both
66/// match it. The return value is ignored -- there is nothing a destructor could
67/// usefully do with a close failure.
68pub type WaitCloseFn = unsafe extern "system" fn(HANDLE) -> BOOL;
69
70/// Owns a handle that is closed with a routine other than `CloseHandle`.
71///
72/// The `Drop` lives here rather than on [`WaitTarget`] so that the enum itself
73/// has no destructor and can be taken apart by an ordinary `match`.
74pub(crate) struct CustomClose {
75    raw: HANDLE,
76    close: WaitCloseFn,
77}
78
79impl Drop for CustomClose {
80    fn drop(&mut self) {
81        // SAFETY: the handle was vouched for by `assume_waitable_with` and is
82        // still open -- every owner drains the wait before dropping this, so the
83        // pool is no longer watching it. This runs exactly once, because `Drop`
84        // does.
85        unsafe { (self.close)(self.raw) };
86    }
87}
88
89/// Owns a wait target and closes it with the routine that target requires.
90///
91/// Most handles are closed with `CloseHandle`, which is what a std
92/// [`OwnedHandle`] does, so that stays the default. Some are not: a
93/// `FindFirstChangeNotification` handle must be closed with
94/// `FindCloseChangeNotification`, and closing it the usual way is wrong. The
95/// second variant carries the caller's routine so those targets can be owned by
96/// the pool on the same terms as any other.
97pub(crate) enum WaitTarget {
98    /// The default: closed by [`OwnedHandle`]'s own drop, with `CloseHandle`.
99    Owned(OwnedHandle),
100    /// Closed with the caller-supplied routine.
101    Custom(CustomClose),
102}
103
104// SAFETY: a handle is an opaque OS-owned value, not a pointer into this
105// process, and both variants only ever read it or hand it to a thread-safe Win32
106// call. This restores what the plain `OwnedHandle` field had automatically.
107unsafe impl Send for WaitTarget {}
108unsafe impl Sync for WaitTarget {}
109
110impl WaitTarget {
111    /// The raw handle, for arming the wait and for the callback context.
112    pub(crate) fn raw(&self) -> HANDLE {
113        match self {
114            WaitTarget::Owned(handle) => handle.as_raw_handle(),
115            WaitTarget::Custom(custom) => custom.raw,
116        }
117    }
118
119    /// Borrow the handle, for signalling or inspecting it.
120    pub(crate) fn borrow(&self) -> BorrowedHandle<'_> {
121        match self {
122            WaitTarget::Owned(handle) => handle.as_handle(),
123            // SAFETY: the handle stays open for as long as `self` owns it, and
124            // the returned borrow cannot outlive that.
125            WaitTarget::Custom(custom) => unsafe { BorrowedHandle::borrow_raw(custom.raw) },
126        }
127    }
128}
129
130impl std::fmt::Debug for WaitTarget {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        // The close routine is a code pointer with no useful rendering.
133        f.debug_struct("WaitTarget")
134            .field("raw", &self.raw())
135            .field(
136                "close",
137                &match self {
138                    WaitTarget::Owned(_) => "CloseHandle",
139                    WaitTarget::Custom(_) => "custom",
140                },
141            )
142            .finish()
143    }
144}
145
146/// A handle the thread pool is able to wait on.
147///
148/// The pool does not support every waitable object: a mutex handle in
149/// particular produces undefined behaviour rather than an error. Requiring this
150/// type instead of a bare [`OwnedHandle`] moves that precondition from prose
151/// into the type system, so a safe caller cannot reach the undefined case.
152///
153/// Construct one safely with [`WaitableHandle::event`], or vouch for a handle
154/// obtained elsewhere with the narrow [`WaitableHandle::assume_waitable`] seam --
155/// or [`WaitableHandle::assume_waitable_with`] when the handle needs a close
156/// routine other than `CloseHandle`.
157/// This mirrors `UnassociatedEndpoint` in `windows-overlapped-io-sys`, which
158/// pairs a safe `open` with an `assume_overlapped` escape hatch for the same
159/// reason.
160#[derive(Debug)]
161pub struct WaitableHandle {
162    target: WaitTarget,
163}
164
165impl WaitableHandle {
166    /// Create an event and wrap it as a waitable handle.
167    ///
168    /// An event is always a supported wait target, so this needs no `unsafe`.
169    /// A `manual_reset` event stays signalled until it is reset; an auto-reset
170    /// event returns to unsignalled as soon as one wait is satisfied, which
171    /// makes it the usual choice for handing off work one activation at a time.
172    ///
173    /// # Errors
174    ///
175    /// Returns the error from `CreateEventW`.
176    pub fn event(manual_reset: bool, initially_signalled: bool) -> io::Result<Self> {
177        // SAFETY: creating an unnamed event with default security attributes;
178        // all pointer arguments are null by design.
179        let raw = unsafe {
180            CreateEventW(
181                ptr::null(),
182                if manual_reset { TRUE } else { FALSE },
183                if initially_signalled { TRUE } else { FALSE },
184                ptr::null(),
185            )
186        };
187        if raw.is_null() {
188            return Err(io::Error::last_os_error());
189        }
190        // SAFETY: the call returned a fresh, exclusively owned event handle.
191        Ok(Self {
192            target: WaitTarget::Owned(unsafe { OwnedHandle::from_raw_handle(raw) }),
193        })
194    }
195
196    /// Wrap a handle whose wait support the caller vouches for.
197    ///
198    /// This is the extensibility seam for wait targets this crate cannot create
199    /// itself -- semaphores, waitable timers, processes, threads, console input,
200    /// change notifications, and so on.
201    ///
202    /// # Safety
203    ///
204    /// The caller guarantees that:
205    ///
206    /// - the handle is a waitable object the thread pool supports, and in
207    ///   particular is **not a mutex**, which the SDK does not support and which
208    ///   yields undefined behaviour rather than an error; and
209    /// - ownership transfers exclusively into the returned value, so nothing
210    ///   else closes the handle while a wait on it is pending.
211    #[must_use]
212    pub unsafe fn assume_waitable(handle: OwnedHandle) -> Self {
213        Self {
214            target: WaitTarget::Owned(handle),
215        }
216    }
217
218    /// Wrap a handle that must be closed with a routine other than
219    /// `CloseHandle`.
220    ///
221    /// Some waitable objects have their own destructor: a
222    /// `FindFirstChangeNotification` handle is closed with
223    /// `FindCloseChangeNotification`, and handing it to
224    /// [`assume_waitable`](Self::assume_waitable) --
225    /// which takes a std [`OwnedHandle`] and therefore closes it with
226    /// `CloseHandle` -- would be wrong. `close` is invoked exactly once, and
227    /// only after the wait has been drained, whether the object is torn down by
228    /// [`ThreadpoolWait`]'s own drop or by a
229    /// [`CleanupGroup`](crate::cleanup_group::CleanupGroup) release.
230    ///
231    /// `close` has the shape Win32 close routines already have, so it can be
232    /// passed directly with no wrapper.
233    ///
234    /// # Safety
235    ///
236    /// The caller guarantees that:
237    ///
238    /// - the handle is a waitable object the thread pool supports, and in
239    ///   particular is **not a mutex**, which the SDK does not support and which
240    ///   yields undefined behaviour rather than an error;
241    /// - ownership transfers exclusively into the returned value, so nothing
242    ///   else closes the handle while a wait on it is pending; and
243    /// - `close` is the correct destructor for this handle and is safe to call
244    ///   once on it after the pool has stopped watching it.
245    #[must_use]
246    pub unsafe fn assume_waitable_with(handle: HANDLE, close: WaitCloseFn) -> Self {
247        Self {
248            target: WaitTarget::Custom(CustomClose { raw: handle, close }),
249        }
250    }
251
252    /// Borrow the underlying handle, for signalling or inspecting it.
253    #[must_use]
254    pub fn handle(&self) -> BorrowedHandle<'_> {
255        self.target.borrow()
256    }
257
258    /// Consume the wrapper and recover the owned handle.
259    ///
260    /// # Errors
261    ///
262    /// Returns the wrapper back unchanged when it carries a custom close
263    /// routine (see [`assume_waitable_with`](Self::assume_waitable_with)): an
264    /// [`OwnedHandle`] closes what it holds with `CloseHandle`, which is
265    /// precisely the wrong destructor for such a target, so there is no correct
266    /// value to hand back. The handle is neither closed nor leaked -- ownership
267    /// simply stays where it was.
268    pub fn into_handle(self) -> Result<OwnedHandle, Self> {
269        match self.target {
270            WaitTarget::Owned(handle) => Ok(handle),
271            target @ WaitTarget::Custom(_) => Err(Self { target }),
272        }
273    }
274
275    /// Consume the wrapper and recover the owner, whichever kind it is.
276    pub(crate) fn into_target(self) -> WaitTarget {
277        self.target
278    }
279}
280
281/// Why a wait callback ran.
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum WaitResult {
284    /// The watched handle became signalled.
285    Signalled,
286    /// The timeout given when the wait was armed elapsed first.
287    TimedOut,
288    /// The pool reported a result this crate does not model. The raw value is
289    /// preserved so a caller can inspect it rather than having it discarded.
290    Other(u32),
291}
292
293impl WaitResult {
294    fn from_raw(value: u32) -> Self {
295        match value {
296            wait_status::SIGNALLED => Self::Signalled,
297            WAIT_TIMEOUT => Self::TimedOut,
298            other => Self::Other(other),
299        }
300    }
301}
302
303/// Heap-allocated callback state kept alive for the lifetime of the wait object.
304///
305/// `wait` is filled in after `CreateThreadpoolWait` returns, because rearming
306/// from inside a callback needs the object the callback belongs to.
307struct WaitContext {
308    wait: AtomicIsize,
309    handle: HANDLE,
310    /// How many callers are currently suppressing re-arming: zero means allowed.
311    ///
312    /// Arming takes this lock and does nothing while the count is non-zero, so a
313    /// callback that re-arms cannot start watching again after a disarm from
314    /// outside: without it, a drain could complete with the object armed again,
315    /// and for `Drop` that meant closing the object and freeing its context with
316    /// a fresh callback queued against them.
317    ///
318    /// A count rather than a flag because suppression has two users with
319    /// different lifetimes: [`ThreadpoolWait::stop_and_drain`] raises it and
320    /// lowers it again, while `Drop` raises it permanently. With a flag, a
321    /// `stop_and_drain` finishing would clear a suppression that another
322    /// concurrent one still needed.
323    ///
324    /// The lock is only ever held across the native `SetThreadpoolWait` call,
325    /// never across a callback drain, which would deadlock a callback that
326    /// happened to be blocked on it.
327    suppress_rearm: Mutex<u32>,
328    callback: Box<dyn Fn(&WaitActivation<'_>) + Send + Sync + 'static>,
329}
330
331impl WaitContext {
332    /// Lock the suppression count, recovering from a panicking holder.
333    fn suppression(&self) -> std::sync::MutexGuard<'_, u32> {
334        self.suppress_rearm
335            .lock()
336            .unwrap_or_else(|poison| poison.into_inner())
337    }
338
339    /// Start suppressing re-arming, and disarm under the same acquisition.
340    ///
341    /// Doing both under one lock is what makes the pair atomic against a
342    /// callback: a re-arm either lands entirely before this, or is suppressed by
343    /// it. The lock is released before any drain.
344    fn suppress_and_disarm(&self) {
345        let mut suppressed = self.suppression();
346        *suppressed = suppressed.saturating_add(1);
347        let wait = self.wait.load(Ordering::Acquire);
348        crate::trace_record!("wait", "suppress-and-disarm", wait, *suppressed);
349        if wait != 0 {
350            // SAFETY: `wait` is this object's live PTP_WAIT, published before any
351            // callback could run and valid until Drop closes it.
352            unsafe { disarm_raw(wait) };
353        }
354    }
355
356    /// Stop suppressing re-arming.
357    fn release_suppression(&self) {
358        let mut suppressed = self.suppression();
359        *suppressed = suppressed.saturating_sub(1);
360    }
361}
362
363// SAFETY: `handle` is a raw handle owned by the ThreadpoolWait that outlives
364// this context; it is only passed back to SetThreadpoolWait, never closed here.
365unsafe impl Send for WaitContext {}
366unsafe impl Sync for WaitContext {}
367
368/// One activation of a [`ThreadpoolWait`], handed to its callback.
369///
370/// The wait is not armed when the callback runs. Call [`WaitActivation::rearm`]
371/// to watch the handle again; doing nothing leaves the wait idle.
372pub struct WaitActivation<'ctx> {
373    result: WaitResult,
374    ctx: &'ctx WaitContext,
375}
376
377impl std::fmt::Debug for WaitActivation<'_> {
378    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379        // The context holds the boxed callback and the raw wait object, neither
380        // of which is meaningful to a reader; the result is the whole story.
381        f.debug_struct("WaitActivation")
382            .field("result", &self.result)
383            .finish_non_exhaustive()
384    }
385}
386
387impl WaitActivation<'_> {
388    /// Why this callback ran.
389    #[must_use]
390    pub fn result(&self) -> WaitResult {
391        self.result
392    }
393
394    /// Whether the watched handle was signalled.
395    #[must_use]
396    pub fn is_signalled(&self) -> bool {
397        self.result == WaitResult::Signalled
398    }
399
400    /// Borrow the handle this activation was for.
401    ///
402    /// The wait owns the handle and outlives every callback, so it is open for
403    /// the duration of this borrow. This is what makes the documented way out of
404    /// the overlap hazard on [`rearm`](Self::rearm) reachable: a callback
405    /// watching a manual-reset event can reset it before re-arming, so the next
406    /// activation waits for a fresh signal instead of starting immediately
407    /// alongside this one.
408    #[must_use]
409    pub fn handle(&self) -> BorrowedHandle<'_> {
410        // SAFETY: the wait owns this handle and cannot be dropped while a
411        // callback is running, so it is open for at least this borrow.
412        unsafe { BorrowedHandle::borrow_raw(self.ctx.handle) }
413    }
414
415    /// Arm the wait again, so the next signal or timeout activates it.
416    ///
417    /// `timeout` of `None` waits indefinitely. This is the mechanism the SDK
418    /// requires for repeated waits: an activation consumes the arming, so a
419    /// callback that wants to keep watching must rearm from inside itself.
420    ///
421    /// # This can overlap the callback with itself
422    ///
423    /// Re-arming takes effect immediately, and the pool activates as soon as the
424    /// handle is signalled. If the handle is *still signalled* when this is
425    /// called -- which is the normal state of a manual-reset event -- the next
426    /// activation is queued at once and can begin before the current callback
427    /// returns. Re-arming early in a long callback therefore runs it
428    /// concurrently with itself, repeatedly: a 20ms callback that re-armed at
429    /// its start was measured entering 7529 times in 400ms, 5110 of those
430    /// overlapping an earlier entry.
431    ///
432    /// This is not the guarantee [`TimerFiring::rearm_after`] gives. A one-shot
433    /// timer's re-arm is deferred until the callback returns, precisely so
434    /// firings stay sequential; a wait's re-arm is not, because the SDK requires
435    /// the wait to be re-armed for the handle's *current* signal state to be
436    /// observed.
437    ///
438    /// Either reset the handle before re-arming, using
439    /// [`handle`](Self::handle), so the next activation waits for a fresh
440    /// signal:
441    ///
442    /// ```no_run
443    /// # use std::os::windows::io::AsRawHandle;
444    /// # use windows_sys::Win32::System::Threading::ResetEvent;
445    /// # fn example(activation: &windows_threadpool_sys::wait::WaitActivation<'_>) {
446    /// // SAFETY: the wait owns the event, so the handle is open here.
447    /// unsafe { ResetEvent(activation.handle().as_raw_handle()) };
448    /// activation.rearm(None);
449    /// # }
450    /// ```
451    ///
452    /// or accept the concurrency and make everything the callback touches
453    /// tolerate it. An auto-reset event does not have this problem, because the
454    /// wait consumes the signal.
455    ///
456    /// [`TimerFiring::rearm_after`]: crate::timer::TimerFiring::rearm_after
457    ///
458    /// # Re-arm before the next signal
459    ///
460    /// The ordering rule described on [`ThreadpoolWait::arm`] applies to
461    /// every arming, not just the first -- `SetThreadpoolWait` says the event
462    /// must be re-registered "before signaling it each time". A producer that
463    /// signals in the window after an activation consumed the arming but
464    /// before this call re-establishes it may not get a callback for that
465    /// signal.
466    ///
467    /// Re-arming *before* draining closes that window, at the cost of
468    /// callbacks that find nothing to do; draining first and re-arming after
469    /// leaves it open. A caller who cannot order the two can drain, re-arm,
470    /// then drain again, so that anything which landed in the window is
471    /// picked up by the second pass rather than waited for.
472    ///
473    /// # Teardown
474    ///
475    /// Re-arming after the object has begun tearing down does nothing, so a
476    /// callback racing [`ThreadpoolWait`]'s `Drop` cannot leave the object armed
477    /// behind it.
478    pub fn rearm(&self, timeout: Option<Duration>) {
479        let _ = self.rearm_reporting(timeout);
480    }
481
482    /// [`rearm`](Self::rearm), reporting whether the arming actually happened.
483    ///
484    /// Returns `false` when the request was suppressed because the object is
485    /// tearing down. The public entry point discards this, because a caller
486    /// cannot act on it: by the time it could look, the object is gone. Tests
487    /// use it to observe the suppression directly, which is otherwise only
488    /// visible as the absence of undefined behaviour.
489    pub(crate) fn rearm_reporting(&self, timeout: Option<Duration>) -> bool {
490        // Taken before arming and held across it, so this either happens before
491        // a suppressing caller raises the count or is suppressed by it -- never
492        // in between.
493        let suppressed = self.ctx.suppression();
494        if *suppressed > 0 {
495            return false;
496        }
497        let wait = self.ctx.wait.load(Ordering::Acquire);
498        debug_assert_ne!(
499            wait, 0,
500            "the wait object must be published before callbacks"
501        );
502        // SAFETY: `wait` is this object's live PTP_WAIT, published before any
503        // callback could run, and `handle` is owned by that object so it is
504        // still open. The timeout, if any, is a live stack value for the call.
505        unsafe { arm_raw(wait, self.ctx.handle, timeout) };
506        drop(suppressed);
507        true
508    }
509}
510
511/// Stop a raw wait object.
512///
513/// SAFETY: `wait` must be a live `PTP_WAIT`.
514pub(crate) unsafe fn disarm_raw(wait: PTP_WAIT) {
515    // SAFETY: forwarded; a null handle is the documented way to cancel a wait.
516    unsafe { SetThreadpoolWait(wait, ptr::null_mut(), ptr::null()) };
517}
518
519/// Arm a raw wait object against a borrowed target.
520///
521/// SAFETY: `wait` must be a live `PTP_WAIT` and `target` must stay open until
522/// the wait is disarmed or the object released.
523pub(crate) unsafe fn arm_member(wait: PTP_WAIT, target: &WaitTarget, timeout: Option<Duration>) {
524    // SAFETY: forwarded from this function's own contract.
525    unsafe { arm_raw(wait, target.raw(), timeout) };
526}
527
528/// Arm or disarm a wait object.
529///
530/// SAFETY: `wait` must be a live `PTP_WAIT` and `handle` a live waitable handle
531/// (or null to disarm).
532unsafe fn arm_raw(wait: PTP_WAIT, handle: HANDLE, timeout: Option<Duration>) {
533    match timeout {
534        Some(timeout) => {
535            let filetime = relative_filetime(timeout);
536            // SAFETY: forwarded from this function's contract; `filetime` is
537            // read only for the duration of the call.
538            unsafe { SetThreadpoolWait(wait, handle, &filetime) };
539        }
540        // SAFETY: forwarded; a null timeout means "wait indefinitely".
541        None => unsafe { SetThreadpoolWait(wait, handle, ptr::null()) },
542    }
543    crate::trace_record!("wait", "armed", wait, handle as usize);
544}
545
546/// Trampoline from the raw `PTP_WAIT_CALLBACK` ABI into the boxed closure.
547///
548/// SAFETY: `context` must point to a live [`WaitContext`] for the entire
549/// duration of every callback invocation, which [`ThreadpoolWait`]'s `Drop`
550/// ordering guarantees.
551unsafe extern "system" fn wait_trampoline(
552    _instance: PTP_CALLBACK_INSTANCE,
553    context: *mut core::ffi::c_void,
554    _wait: PTP_WAIT,
555    wait_result: u32,
556) {
557    crate::trace_record!("wait", "trampoline-entered", _wait, wait_result);
558    // SAFETY: context is a valid *mut WaitContext for the full callback duration.
559    let ctx = unsafe { &*(context as *const WaitContext) };
560    let activation = WaitActivation {
561        result: WaitResult::from_raw(wait_result),
562        ctx,
563    };
564    // Not contained: see the callback contract in the crate docs.
565    (ctx.callback)(&activation);
566}
567
568/// An owned thread-pool wait object bound to one waitable handle.
569///
570/// The object owns the handle, so the handle cannot be closed while a wait is
571/// pending. A newly created wait is idle; arm it with [`ThreadpoolWait::arm`],
572/// and rearm from inside the callback with [`WaitActivation::rearm`].
573///
574/// [`Drop`] disarms before draining callbacks, then closes the object and only
575/// afterwards releases the callback context and the handle.
576///
577/// Unlike [`ThreadpoolTimer`](crate::timer::ThreadpoolTimer), **the callback can
578/// run concurrently with itself**: a wait's re-arm takes effect immediately, so
579/// re-arming while the handle is still signalled queues the next activation
580/// before the current callback returns. See [`WaitActivation::rearm`] for the
581/// measurements and the two ways to avoid it.
582///
583/// # Examples
584///
585/// Watch an event once. The wait takes ownership of the handle, and
586/// [`ThreadpoolWait::handle`] borrows it back for signalling:
587///
588/// ```
589/// use std::os::windows::io::AsRawHandle;
590/// use std::sync::mpsc;
591/// use windows_sys::Win32::System::Threading::SetEvent;
592/// use windows_threadpool_sys::wait::{ThreadpoolWait, WaitResult, WaitableHandle};
593///
594/// let event = WaitableHandle::event(true, false)?;
595///
596/// let (tx, rx) = mpsc::channel();
597/// let sender = std::sync::Mutex::new(tx);
598/// let wait = ThreadpoolWait::new(event, move |activation| {
599///     let _ = sender.lock().expect("send").send(activation.result());
600/// }, None)?;
601///
602/// wait.arm(None);
603/// // SAFETY: the wait owns the event, so the handle is still open.
604/// unsafe { SetEvent(wait.handle().as_raw_handle()) };
605///
606/// assert_eq!(rx.recv().expect("activation"), WaitResult::Signalled);
607/// # Ok::<(), std::io::Error>(())
608/// ```
609///
610/// Keep watching across activations by rearming from inside the callback, which
611/// is what the SDK requires -- an activation consumes the arming:
612///
613/// ```
614/// # use std::os::windows::io::AsRawHandle;
615/// # use std::sync::Arc;
616/// # use std::sync::atomic::{AtomicUsize, Ordering};
617/// # use windows_sys::Win32::System::Threading::SetEvent;
618/// use windows_threadpool_sys::wait::{ThreadpoolWait, WaitableHandle};
619///
620/// let event = WaitableHandle::event(false, false)?;
621///
622/// let seen = Arc::new(AtomicUsize::new(0));
623/// let counter = Arc::clone(&seen);
624/// let wait = ThreadpoolWait::new(event, move |activation| {
625///     counter.fetch_add(1, Ordering::SeqCst);
626///     activation.rearm(None);
627/// }, None)?;
628///
629/// wait.arm(None);
630/// for _ in 0..3 {
631///     // SAFETY: the wait owns the event, so the handle is still open.
632///     unsafe { SetEvent(wait.handle().as_raw_handle()) };
633///     std::thread::sleep(std::time::Duration::from_millis(5));
634/// }
635///
636/// wait.disarm();
637/// wait.wait();
638/// assert!(seen.load(Ordering::SeqCst) >= 1);
639/// # Ok::<(), std::io::Error>(())
640/// ```
641pub struct ThreadpoolWait {
642    wait: PTP_WAIT,
643    target: WaitTarget,
644    // Kept alive as a raw pointer until Drop has disarmed and drained.
645    context: *mut WaitContext,
646}
647
648// SAFETY: PTP_WAIT is a cross-thread pool object, WaitTarget is Send + Sync,
649// and the context is Send + Sync; the pointer is only read until Drop frees it
650// after all callbacks have finished.
651unsafe impl Send for ThreadpoolWait {}
652unsafe impl Sync for ThreadpoolWait {}
653
654impl ThreadpoolWait {
655    /// Create an idle wait watching `handle`.
656    ///
657    /// The object takes ownership of the handle and closes it on drop, which is
658    /// what guarantees the handle outlives any pending wait.
659    ///
660    /// Pass `Some(env)` to select a private pool or callback priority; `None`
661    /// uses the process-default pool with default priority.
662    ///
663    /// The callback runs on a shared, process-managed pool thread, must restore
664    /// any thread state it changes, and must not terminate its thread. It must
665    /// not panic: a panic unwinds to the `extern "system"` trampoline and aborts
666    /// the process.
667    ///
668    /// Taking a [`WaitableHandle`] rather than a bare handle is what keeps this
669    /// constructor safe: the thread pool does not support every waitable object,
670    /// and a mutex handle in particular is undefined rather than an error.
671    ///
672    /// # Errors
673    ///
674    /// Returns the error from `CreateThreadpoolWait`.
675    pub fn new<F>(
676        handle: WaitableHandle,
677        callback: F,
678        env: Option<&mut CallbackEnviron<'_>>,
679    ) -> io::Result<Self>
680    where
681        F: Fn(&WaitActivation<'_>) + Send + Sync + 'static,
682    {
683        let target = handle.into_target();
684        let context = Box::into_raw(Box::new(WaitContext {
685            wait: AtomicIsize::new(0),
686            handle: target.raw(),
687            suppress_rearm: Mutex::new(0),
688            callback: Box::new(callback),
689        }));
690        let env_ptr = env.map_or(ptr::null_mut(), |e| e.as_mut_ptr());
691
692        // SAFETY: context is a valid heap pointer that outlives every callback,
693        // and env_ptr is valid (or null) for the duration of this call.
694        let wait = unsafe {
695            CreateThreadpoolWait(Some(wait_trampoline), context.cast(), env_ptr.cast_const())
696        };
697
698        if wait == 0 {
699            let error = io::Error::last_os_error();
700            // SAFETY: the pool never saw context; reclaim it immediately.
701            unsafe { drop(Box::from_raw(context)) };
702            return Err(error);
703        }
704
705        // Publish the object before any callback can run. No wait is armed yet,
706        // so no callback can observe the unpublished value.
707        // SAFETY: context is live and exclusively ours until the first arming.
708        unsafe { (*context).wait.store(wait, Ordering::Release) };
709        crate::trace_record!("wait", "created", wait, target.raw() as usize);
710
711        Ok(Self {
712            wait,
713            target,
714            context,
715        })
716    }
717
718    /// Borrow the watched handle, for signalling or inspecting it.
719    #[must_use]
720    pub fn handle(&self) -> BorrowedHandle<'_> {
721        self.target.borrow()
722    }
723
724    /// Arm the wait, so the next signal or timeout runs the callback once.
725    ///
726    /// `timeout` of `None` waits indefinitely. Arming replaces any previous
727    /// arming rather than adding to it, and an activation consumes the arming --
728    /// rearm from inside the callback with [`WaitActivation::rearm`] to keep
729    /// watching.
730    ///
731    /// # Arm before you signal
732    ///
733    /// `SetThreadpoolWait` documents that "you must re-register the event with
734    /// the wait object before signaling it each time to trigger the wait
735    /// callback". Signal a handle that is not currently armed -- including in
736    /// the window between constructing a [`ThreadpoolWait`] and this call --
737    /// and the callback is not guaranteed to run for that signal.
738    ///
739    /// An auto-reset event makes a dropped signal permanent rather than merely
740    /// late, because the signal is consumed and there is nothing left for a
741    /// subsequent arming to observe. If the handle is only ever signalled once,
742    /// as a wakeup for state that is already present, that lost signal is the
743    /// last one the waiter will ever get.
744    ///
745    /// Every example in this module arms first for that reason; so does every
746    /// caller in this workspace. A caller who cannot control the ordering
747    /// should watch a manual-reset event kept in agreement with the state it
748    /// reports, which is level-triggered and so has no signal to lose.
749    pub fn arm(&self, timeout: Option<Duration>) {
750        // SAFETY: `wait` is valid for the lifetime of self, and the handle is
751        // owned by self so it is still open.
752        unsafe { arm_raw(self.wait, self.target.raw(), timeout) };
753    }
754
755    /// Stop watching.
756    ///
757    /// New activations stop being queued, but a callback already queued still
758    /// runs; use [`ThreadpoolWait::cancel_pending`] to drop those as well.
759    pub fn disarm(&self) {
760        // SAFETY: `wait` is valid for the lifetime of self; a null handle is the
761        // documented way to cancel a pending wait.
762        unsafe { SetThreadpoolWait(self.wait, ptr::null_mut(), ptr::null()) };
763    }
764
765    /// Let every queued callback run, and block until none is executing.
766    ///
767    /// This does **not** leave a self-re-arming wait idle: a callback running
768    /// during this call can [`rearm`](WaitActivation::rearm) before it returns,
769    /// so the object is watching again when this returns. Use
770    /// [`stop_and_drain`](Self::stop_and_drain) to reach quiescence.
771    pub fn wait(&self) {
772        // SAFETY: `wait` is valid for the lifetime of self.
773        unsafe { WaitForThreadpoolWaitCallbacks(self.wait, FALSE) };
774    }
775
776    /// Drop callbacks that have not started, then wait for any executing one.
777    ///
778    /// Like [`wait`](Self::wait), this does not by itself leave a self-re-arming
779    /// wait idle: it does not suppress the re-arm of a callback that is already
780    /// running. Use [`stop_and_drain`](Self::stop_and_drain) when the wait must
781    /// actually be quiescent afterwards.
782    pub fn cancel_pending(&self) {
783        // SAFETY: `wait` is valid for the lifetime of self. A cancelled wait
784        // callback owns no storage, so dropping queued callbacks orphans nothing.
785        unsafe { WaitForThreadpoolWaitCallbacks(self.wait, TRUE) };
786    }
787
788    /// Stop watching and block until the wait is idle, leaving it reusable.
789    ///
790    /// This exists because neither [`disarm`](Self::disarm) nor
791    /// [`cancel_pending`](Self::cancel_pending) can stop a self-re-arming wait on
792    /// its own: a callback already running can call [`WaitActivation::rearm`]
793    /// after a disarm from outside has taken effect. This suppresses re-arming
794    /// for the duration of the call, using the same mechanism `Drop` uses, and
795    /// lifts the suppression before returning so the wait can be armed again.
796    ///
797    /// # What this guarantees
798    ///
799    /// On return, provided no other thread arms the wait during the call:
800    ///
801    /// - no callback is queued or executing, and
802    /// - the object is not watching -- a re-arm requested by a callback that ran
803    ///   during the call is discarded rather than deferred.
804    ///
805    /// # What it does not
806    ///
807    /// **A concurrent [`arm`](Self::arm) from another thread is not excluded.**
808    /// `ThreadpoolWait` is `Sync` and `arm` takes `&self`, so it does not pass
809    /// through the suppression this uses, and nothing in this crate orders such
810    /// a call against this one. A caller needing the wait to be provably idle
811    /// must ensure nothing else arms it for the duration, by owning it
812    /// exclusively or serializing access to it.
813    ///
814    /// Calling this from inside the wait's own callback would deadlock, because
815    /// it waits for that callback to finish.
816    pub fn stop_and_drain(&self) {
817        // SAFETY: the context outlives every callback and is freed only by Drop,
818        // which cannot run while this borrow of self is alive.
819        let ctx = unsafe { &*self.context };
820        ctx.suppress_and_disarm();
821        // Drained with the lock released: a callback blocked on it would
822        // otherwise never finish, and this would never return.
823        self.cancel_pending();
824        ctx.release_suppression();
825    }
826
827    /// Give up ownership, returning the raw object, its callback context, and
828    /// the watched target.
829    ///
830    /// Used only by [`crate::cleanup_group::CleanupGroup`], which takes over all
831    /// three. The target must go with them: the pool may still be watching it
832    /// until the group releases the member, so it cannot be closed when the
833    /// borrowing member goes out of scope.
834    pub(crate) fn into_parts(self) -> (PTP_WAIT, *mut core::ffi::c_void, WaitTarget) {
835        let this = std::mem::ManuallyDrop::new(self);
836        // SAFETY: `this` is never dropped, so moving the target out cannot be
837        // observed by a later drop of the original value.
838        let target = unsafe { ptr::read(&this.target) };
839        (this.wait, this.context.cast(), target)
840    }
841
842    /// Free a context returned by [`ThreadpoolWait::into_parts`].
843    ///
844    /// # Safety
845    ///
846    /// `context` must come from `into_parts` on this type, its object must
847    /// already have been released, and it must be freed exactly once.
848    pub(crate) unsafe fn drop_context(context: *mut core::ffi::c_void) {
849        // SAFETY: forwarded from this function's own contract.
850        drop(unsafe { Box::from_raw(context.cast::<WaitContext>()) });
851    }
852
853    /// Suppress this member's re-arm and disarm it, before a
854    /// [`crate::cleanup_group::CleanupGroup`] bulk-releases its members.
855    ///
856    /// `CloseThreadpoolCleanupGroupMembers` waits for executing callbacks but
857    /// does not stop one from re-arming: a callback still running can call
858    /// [`WaitActivation::rearm`] after the bulk release has begun, which would
859    /// re-arm an object being torn down and then free its context under a freshly
860    /// queued callback. Raising the suppression before the release closes that
861    /// door, exactly as this type's own `Drop` does; the suppression is never
862    /// lifted because the member is being destroyed.
863    ///
864    /// # Safety
865    ///
866    /// `context` must come from [`into_parts`](Self::into_parts) on this type
867    /// and name a still-live object whose context the caller has not yet freed.
868    pub(crate) unsafe fn prepare_shutdown(context: *mut core::ffi::c_void) {
869        // SAFETY: forwarded; the context outlives the member until the group
870        // frees it, and `suppress_and_disarm` only touches this object.
871        let ctx = unsafe { &*context.cast::<WaitContext>() };
872        ctx.suppress_and_disarm();
873    }
874}
875
876impl Drop for ThreadpoolWait {
877    fn drop(&mut self) {
878        // Close the door on re-arming before disarming, and do both under the
879        // same lock. Disarming alone is not enough: a callback already running
880        // could re-arm afterwards, the drain below could then return with the
881        // object armed, and the close and context free would race a freshly
882        // queued callback.
883        // SAFETY: the context outlives every callback; Drop frees it below,
884        // after the drain.
885        let ctx = unsafe { &*self.context };
886        // Raised and never released: unlike `stop_and_drain`, there is no
887        // afterwards for this object.
888        crate::trace_record!("wait", "drop-begin", self.wait);
889        ctx.suppress_and_disarm();
890        // The lock is released before draining: a callback blocked on it would
891        // otherwise never finish, and this wait would never return.
892        self.cancel_pending();
893        crate::trace_record!("wait", "drop-drained", self.wait);
894
895        // SAFETY: no callback can be queued or executing, so the object can be
896        // closed and the context freed exactly once. `target` is dropped after
897        // this, when its field is dropped, so the handle outlives the wait
898        // object and its close routine -- `CloseHandle` or a custom one -- runs
899        // only once the pool has stopped watching it.
900        unsafe {
901            CloseThreadpoolWait(self.wait);
902            drop(Box::from_raw(self.context));
903        }
904        crate::trace_record!("wait", "drop-closed", self.wait);
905    }
906}
907
908#[cfg(test)]
909mod tests;