Skip to main content

dolang_winterop/
apc.rs

1//! Low-level async integration with Windows APCs
2//!
3//! Some Win32 APIs deliver their completion as a user-mode APC to whichever thread made the call,
4//! and require that thread to periodically enter an alertable wait
5//! (`SleepEx`/`WaitForSingleObjectEx` with `bAlertable = TRUE`). This module is deliberately
6//! agnostic to any particular such API: it only provides the alertable thread, task creation, and
7//! cooperative cancellation.
8//!
9//! # Cancellation
10//!
11//! Dropping the [`Task`] returned by [`Reactor::submit`] cancels the task. By default this simply
12//! drops the corresponding future in the reactor thread. A task that needs to do something before
13//! being torn down can call [`Context::cancel_guard`], which turns a cancellation request arriving
14//! during that region into a cooperative `Err` instead of a drop, so the task's own code can run
15//! async cleanup before finishing normally.
16
17use std::{error, fmt, io};
18
19/// A future boxed for storage on the reactor thread. Deliberately not
20/// `Send`: it is only ever constructed and polled on the reactor thread
21/// itself (see [`Reactor::submit`]), never transported across a thread
22/// boundary — which also sidesteps `AsyncFnOnce`'s associated future type
23/// not being nameable as `Send` on stable Rust.
24#[cfg(all(windows, not(docsrs)))]
25mod imp {
26    use super::*;
27    pub(super) use futures::{
28        channel::oneshot,
29        future::{self, Either},
30        task::ArcWake,
31    };
32    pub(super) use std::{
33        cell::RefCell,
34        collections::HashMap,
35        os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle},
36        panic::{AssertUnwindSafe, catch_unwind},
37        pin::Pin,
38        ptr,
39        sync::{
40            Arc, Mutex, Weak,
41            atomic::{AtomicU64, Ordering},
42            mpsc,
43        },
44        task::{self, Poll, Waker},
45        thread,
46    };
47    pub(super) use windows_sys::Win32::{
48        Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, TRUE},
49        System::Threading::{GetCurrentProcess, GetCurrentThread, INFINITE, QueueUserAPC, SleepEx},
50    };
51
52    pub(super) type BoxedTask = Pin<Box<dyn Future<Output = ()>>>;
53
54    /// Uniquely identifies a task within a single [`Reactor`]'s registry.
55    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56    pub(super) struct TaskId(pub(super) u64);
57
58    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
59    pub struct Closed;
60
61    impl fmt::Display for Closed {
62        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63            f.write_str("apc reactor is closed")
64        }
65    }
66
67    impl error::Error for Closed {}
68
69    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
70    pub struct TaskCanceled;
71
72    impl fmt::Display for TaskCanceled {
73        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74            f.write_str("apc task was cancelled")
75        }
76    }
77
78    impl error::Error for TaskCanceled {}
79
80    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
81    pub struct Canceled;
82
83    impl fmt::Display for Canceled {
84        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85            f.write_str("apc task was cancelled inside a cancel_guard")
86        }
87    }
88
89    impl error::Error for Canceled {}
90
91    /// A per-task registry slot. Only ever touched by the reactor thread.
92    pub(super) struct TaskSlot {
93        /// Taken out for the duration of each poll so that reentrant access to
94        /// this same slot's other fields (e.g. from `cancel_guard`, which runs
95        /// as part of polling the task's own future) doesn't need a reentrant
96        /// `RefCell` borrow.
97        pub(super) future: Option<BoxedTask>,
98        pub(super) in_guard: bool,
99        pub(super) guard_signal: Option<oneshot::Sender<()>>,
100    }
101
102    #[derive(Default)]
103    pub(super) struct Registry {
104        pub(super) tasks: HashMap<TaskId, TaskSlot>,
105        /// Set by the reactor's own flush-marker APC (see `run`) to whatever
106        /// `tasks`'s emptiness actually was at the moment that marker ran.
107        /// This, not some value independently recomputed by the main loop, is
108        /// the real exit condition: a task-insertion (or any other) APC can be
109        /// durably queued without having run yet, in which case `tasks`
110        /// doesn't reflect it yet and looks empty when it isn't really. The
111        /// flush marker always runs strictly after anything queued before it
112        /// (the OS's per-thread APC queue is FIFO), so its own check is
113        /// authoritative for that instant.
114        pub(super) should_exit: bool,
115    }
116
117    #[cfg(windows)]
118    thread_local! {
119        /// Owned by the reactor thread's loop. Cross-thread requests
120        /// (submission, cancellation) always arrive as a closure posted via a
121        /// real `QueueUserAPC`, which only actually runs once executing on this
122        /// thread — so nothing here needs locking.
123        pub(super) static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
124    }
125
126    /// Queues `f` to run on the thread identified by `handle`, via a real
127    /// `QueueUserAPC`. This is the raw mechanism — no synchronization against
128    /// the reactor thread's own shutdown decision. Only [`ReactorInner::drop`],
129    /// [`Control::close`], and `run`'s own flush marker (see `run`) are
130    /// allowed to call this directly, since they can each prove nothing else
131    /// could be racing them; every other caller must go through [`post`], which
132    /// guards against a "successfully" queued APC being silently discarded when
133    /// the thread terminates before ever running it.
134    ///
135    /// Takes a raw `HANDLE` rather than `&OwnedHandle` so `run` can pass the
136    /// pseudo-handle from `GetCurrentThread()` (valid only for the calling
137    /// thread to refer to itself, not an owned resource to be duplicated or
138    /// closed) when posting its own flush marker.
139    ///
140    /// # Safety
141    ///
142    /// `handle` must be a valid thread handle (with `THREAD_SET_CONTEXT`
143    /// access) for the entire duration of this call — either a real `HANDLE`
144    /// the caller keeps open (e.g. via a live `OwnedHandle` it's borrowing
145    /// from), or the `GetCurrentThread()` pseudo-handle used from within the
146    /// thread it refers to.
147    pub(super) unsafe fn queue_apc(
148        handle: HANDLE,
149        f: impl FnOnce() + Send + 'static,
150    ) -> io::Result<()> {
151        unsafe extern "system" fn trampoline(param: usize) {
152            // SAFETY: `param` was produced by `Box::into_raw` below, from a
153            // `Box<Box<dyn FnOnce() + Send>>` that hasn't been freed yet (this
154            // is the only place that ever reconstructs or frees it).
155            let boxed = unsafe { Box::from_raw(param as *mut Box<dyn FnOnce() + Send>) };
156            // Catch panics here: this runs across an `extern "system"`
157            // boundary, where unwinding is undefined behavior. A panicking
158            // closure (a bug in an injected task-insertion or cancel-dispatch
159            // closure, say) shouldn't be able to bring down the whole process.
160            let _ = catch_unwind(AssertUnwindSafe(move || (*boxed)()));
161        }
162
163        let boxed: Box<dyn FnOnce() + Send> = Box::new(f);
164        let raw = Box::into_raw(Box::new(boxed));
165        // SAFETY: `raw` is a valid, uniquely-owned pointer we just created;
166        // `trampoline` reconstructs and consumes it exactly once, whenever the
167        // OS actually delivers this APC.
168        let ok = unsafe {
169            QueueUserAPC(
170                Some(trampoline as unsafe extern "system" fn(usize)),
171                handle,
172                raw as usize,
173            )
174        };
175        if ok == 0 {
176            // The APC will never run; reclaim the box instead of leaking it.
177            drop(unsafe { Box::from_raw(raw) });
178            return Err(io::Error::last_os_error());
179        }
180        Ok(())
181    }
182
183    /// Mutex-guarded state shared by every handle to a given reactor. Bundles
184    /// the thread handle together with `closed` so that touching the handle
185    /// for anything that needs to be synchronized with `closed` — which turns
186    /// out to be everything (see [`post`] and `run`) — is structurally forced
187    /// to go through the same lock, rather than relying on each call site to
188    /// separately remember to.
189    pub(super) struct ReactorState {
190        pub(super) thread_handle: OwnedHandle,
191        /// Set by [`Control::close`]. Once true, [`Reactor::submit`]
192        /// rejects new work with [`Closed`].
193        pub(super) closed: bool,
194    }
195
196    /// State shared by every handle to a given reactor ([`Reactor`] clones,
197    /// [`Control`], and the [`Context`]/[`Task`] belonging to each
198    /// live task). Wrapped in a single `Arc` so there's one allocation and one
199    /// refcount for the whole reactor rather than three.
200    pub(super) struct ReactorInner {
201        pub(super) state: Mutex<ReactorState>,
202        pub(super) next_id: AtomicU64,
203    }
204
205    impl Drop for ReactorInner {
206        fn drop(&mut self) {
207            // This only runs once every strong reference — every `Reactor`
208            // clone, `Control`, and live task — is gone. [`post`] wraps
209            // every closure it queues to hold its own `Arc<ReactorInner>` for
210            // as long as it's queued-but-undelivered, so nothing can possibly
211            // still be in flight at this point — a plain, unconditional wake is
212            // enough to get the reactor thread to notice, via `Weak::upgrade`
213            // failing in `run`, and exit. Unlike the explicit-`close` path, no
214            // flush-and-recheck is needed here: nothing can still be racing us
215            // (the only other way the reactor thread could be gone is this
216            // very drop, which can't have run twice), and the handle is still
217            // open at this point — only after this function returns does Rust
218            // drop it (closing it) as an ordinary field.
219            let guard = self.state.lock().unwrap();
220            // SAFETY: `guard.thread_handle` is a live `OwnedHandle`, kept open
221            // by holding `guard` (this field isn't dropped until this
222            // function returns) for the duration of this call.
223            let _ = unsafe { queue_apc(guard.thread_handle.as_raw_handle() as HANDLE, || {}) };
224        }
225    }
226
227    /// Queues `f` to run on the reactor thread.
228    ///
229    /// Doesn't need to check `closed` or otherwise synchronize with the reactor
230    /// thread's own shutdown decision, nor keep the reactor alive itself while
231    /// queued-but-undelivered: `run`'s loop never actually stops until its own
232    /// flush marker confirms the task registry is empty, and that marker is
233    /// always processed strictly after anything already durably queued at the
234    /// time it's posted (the OS's per-thread APC queue is FIFO) — so an APC
235    /// queued while the reactor thread is still willing to accept it is
236    /// guaranteed to run before the reactor exits, full stop.
237    pub(super) fn post(
238        inner: &Arc<ReactorInner>,
239        f: impl FnOnce() + Send + 'static,
240    ) -> io::Result<()> {
241        let guard = inner.state.lock().unwrap();
242        // SAFETY: `guard.thread_handle` is a live `OwnedHandle`, kept open by
243        // holding `guard` for the duration of this call.
244        unsafe { queue_apc(guard.thread_handle.as_raw_handle() as HANDLE, f) }
245    }
246
247    pub(super) fn close_reactor(inner: &ReactorInner) {
248        let mut guard = inner.state.lock().unwrap();
249        if guard.closed {
250            return;
251        }
252        guard.closed = true;
253        // SAFETY: `guard.thread_handle` is a live `OwnedHandle`, kept open by
254        // holding `guard` for the duration of this call.
255        let _ = unsafe { queue_apc(guard.thread_handle.as_raw_handle() as HANDLE, || {}) };
256    }
257
258    /// Wakes the reactor thread's alertable wait so it re-polls its task set.
259    /// Shared by every task's [`Context`] — the reactor re-polls its whole
260    /// registry after every wake regardless of cause, so there is no need for
261    /// per-task wake identity.
262    ///
263    /// Holds a `Weak` reference rather than a strong one: a waker can end up
264    /// cloned into and held by some external resource (e.g. a channel a task is
265    /// blocked on) for longer than the task itself, and a strong reference
266    /// there would keep the whole reactor alive even after every real handle to
267    /// it (`Reactor`, `Control`, the task itself) is gone.
268    pub(super) struct WakeSignal {
269        pub(super) inner: Weak<ReactorInner>,
270    }
271
272    impl ArcWake for WakeSignal {
273        fn wake_by_ref(arc_self: &Arc<Self>) {
274            // Best effort: failure, or the upgrade failing, means the reactor
275            // thread has already exited (or is about to), so there is nothing
276            // left to wake.
277            if let Some(inner) = arc_self.inner.upgrade() {
278                let _ = post(&inner, || {});
279            }
280        }
281    }
282
283    #[derive(Clone)]
284    pub struct Reactor {
285        pub(super) inner: Arc<ReactorInner>,
286    }
287
288    pub struct Control {
289        pub(super) inner: Arc<ReactorInner>,
290        pub(super) exit_rx: oneshot::Receiver<()>,
291    }
292
293    pub struct Join {
294        pub(super) inner: Option<Weak<ReactorInner>>,
295        pub(super) exit_rx: oneshot::Receiver<()>,
296        pub(super) _pin: std::marker::PhantomPinned,
297    }
298
299    pub struct Context {
300        pub(super) id: TaskId,
301        pub(super) inner: Arc<ReactorInner>,
302    }
303
304    pub struct Task<T> {
305        pub(super) id: Option<TaskId>,
306        pub(super) rx: oneshot::Receiver<T>,
307        pub(super) inner: Arc<ReactorInner>,
308        pub(super) _pin: std::marker::PhantomPinned,
309    }
310
311    pub(super) fn run(weak_inner: Weak<ReactorInner>) {
312        let waker = futures::task::waker(Arc::new(WakeSignal {
313            inner: weak_inner.clone(),
314        }));
315        loop {
316            // SAFETY: plain alertable wait; no preconditions beyond a valid
317            // calling thread.
318            unsafe {
319                SleepEx(INFINITE, TRUE);
320            }
321            poll_all(&waker);
322
323            if REGISTRY.with(|r| r.borrow().should_exit) {
324                break;
325            }
326
327            // `closed` is either genuinely true (`Control::close` was
328            // called), or *effectively* true because nobody could possibly
329            // call it — or `Reactor::submit` — ever again: `weak_inner.upgrade`
330            // failing means every `Reactor`, `Control`, and live task
331            // reference is gone. Either way this alone doesn't mean it's safe
332            // to stop: there could still be a live task in `registry`, or an
333            // APC already durably queued but not yet reflected there. Keep
334            // looping normally (the `if` below is only a cheap pre-filter, not
335            // the real exit decision) until a flush marker actually confirms
336            // it.
337            let closed = match weak_inner.upgrade() {
338                Some(inner) => inner.state.lock().unwrap().closed,
339                None => true,
340            };
341            if closed && REGISTRY.with(|r| r.borrow().tasks.is_empty()) {
342                // Posted via the `GetCurrentThread()` pseudo-handle, not
343                // `ReactorInner`'s — which may already be gone in the
344                // natural-quiescence case — since this must keep working
345                // regardless. Its own check of `registry`, made at the moment
346                // it actually runs (strictly after anything already durably
347                // queued, per FIFO order — see `post`), is what's actually
348                // authoritative; if something did sneak in, `should_exit`
349                // simply comes out false and the loop above keeps running
350                // normally until this is attempted again once things settle.
351                // SAFETY: `GetCurrentThread()`'s pseudo-handle is always valid
352                // for the thread it refers to, which is the one making this
353                // call.
354                let _ = unsafe {
355                    queue_apc(GetCurrentThread(), || {
356                        REGISTRY.with(|r| {
357                            let mut r = r.borrow_mut();
358                            r.should_exit = r.tasks.is_empty();
359                        });
360                    })
361                };
362            }
363        }
364    }
365
366    fn poll_all(waker: &Waker) {
367        let ids: Vec<TaskId> = REGISTRY.with(|r| r.borrow().tasks.keys().copied().collect());
368        for id in ids {
369            let future = REGISTRY.with(|r| {
370                r.borrow_mut()
371                    .tasks
372                    .get_mut(&id)
373                    .and_then(|slot| slot.future.take())
374            });
375            let Some(mut future) = future else {
376                // Not present (already retired) or its future was already
377                // taken by an earlier iteration of this same pass — neither
378                // can happen today since nothing re-enters `poll_all`
379                // mid-pass, but skip defensively rather than panic.
380                continue;
381            };
382
383            let mut cx = task::Context::from_waker(waker);
384            let outcome = catch_unwind(AssertUnwindSafe(|| future.as_mut().poll(&mut cx)));
385
386            match outcome {
387                Ok(Poll::Pending) => {
388                    REGISTRY.with(|r| {
389                        if let Some(slot) = r.borrow_mut().tasks.get_mut(&id) {
390                            slot.future = Some(future);
391                        }
392                        // else: the slot was removed while its future was
393                        // checked out above. Can't happen today (see above),
394                        // but if it did, just let `future` drop here.
395                    });
396                }
397                Ok(Poll::Ready(())) | Err(_) => {
398                    // A panic while polling this task shouldn't take down the
399                    // shared reactor thread or strand other in-flight tasks —
400                    // just drop this one and move on.
401                    let removed = REGISTRY.with(|r| r.borrow_mut().tasks.remove(&id));
402                    drop(removed);
403                    drop(future);
404                }
405            }
406        }
407    }
408}
409
410#[cfg(docsrs)]
411mod imp {
412    use std::{error, fmt};
413
414    struct ReceiverInner {
415        _inner: std::cell::UnsafeCell<()>,
416    }
417
418    unsafe impl Sync for ReceiverInner {}
419
420    struct ReceiverMarker {
421        _inner: std::sync::Arc<ReceiverInner>,
422    }
423
424    // Mirrors the synchronization around the real oneshot receiver: it can
425    // be shared across threads, but is neither `UnwindSafe` nor
426    // `RefUnwindSafe`.
427
428    /// A handle for submitting work to a reactor.
429    ///
430    /// Cloneable.  The reactor thread implicitly shuts down after
431    /// all clones are dropped and no work remains.
432    #[derive(Clone)]
433    pub struct Reactor {
434        _dummy: (),
435    }
436
437    /// Allows closing and awaiting exit of a [`Reactor`].
438    pub struct Control {
439        _marker: ReceiverMarker,
440    }
441
442    /// Future returned by [`Control::join`].
443    pub struct Join {
444        _marker: ReceiverMarker,
445        _pin: std::marker::PhantomPinned,
446    }
447
448    /// Context handle provided to APC tasks.
449    pub struct Context {
450        _dummy: (),
451    }
452
453    /// A future result of a task.
454    ///
455    /// Dropping it before it resolves cancels the task.
456    pub struct Task<T> {
457        _value: std::marker::PhantomData<T>,
458        _marker: ReceiverMarker,
459        _pin: std::marker::PhantomPinned,
460    }
461
462    // The real oneshot receiver is `Sync` when `T: Send`; `T` need not be
463    // `Sync` because access to it is synchronized internally.
464    unsafe impl<T: Send> Sync for Task<T> {}
465
466    /// Error returned by [`Reactor::submit`] when the reactor is closed.
467    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
468    pub struct Closed;
469
470    impl fmt::Display for Closed {
471        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472            f.write_str("apc reactor is closed")
473        }
474    }
475
476    impl error::Error for Closed {}
477
478    /// Error returned when a reactor task is cancelled before producing a value.
479    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
480    pub struct TaskCanceled;
481
482    impl fmt::Display for TaskCanceled {
483        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484            f.write_str("apc task was cancelled")
485        }
486    }
487
488    impl error::Error for TaskCanceled {}
489
490    /// Error returned when cancellation reaches [`Context::cancel_guard`].
491    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
492    pub struct Canceled;
493
494    impl fmt::Display for Canceled {
495        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496            f.write_str("apc task was cancelled inside a cancel_guard")
497        }
498    }
499
500    impl error::Error for Canceled {}
501}
502
503#[cfg(windows)]
504use imp::*;
505pub use imp::{Canceled, Closed, Context, Control, Join, Reactor, Task, TaskCanceled};
506
507#[cfg(any(windows, docsrs))]
508const _: fn() = || {
509    fn assert_send_sync<T: Send + Sync>() {}
510    fn assert_send_sync_unpin<T: Send + Sync + Unpin>() {}
511    fn assert_unwind_safe<T: std::panic::UnwindSafe>() {}
512    fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
513
514    assert_send_sync_unpin::<Reactor>();
515    assert_send_sync_unpin::<Control>();
516    assert_send_sync::<Join>();
517    assert_send_sync_unpin::<Context>();
518    // `Cell<()>` is `Send` but not `Sync`. The oneshot receiver, and therefore
519    // `Task<T>`, is nevertheless `Sync` when `T: Send`.
520    assert_send_sync::<Task<std::cell::Cell<()>>>();
521
522    assert_unwind_safe::<Reactor>();
523    assert_unwind_safe::<Context>();
524    assert_ref_unwind_safe::<Reactor>();
525    assert_ref_unwind_safe::<Context>();
526};
527
528impl Reactor {
529    /// Spawns the reactor thread, returning a cloneable submission handle
530    /// alongside the unique handle that controls its lifecycle.
531    pub async fn new() -> io::Result<(Reactor, Control)> {
532        #[cfg(windows)]
533        {
534            let (exit_tx, exit_rx) = oneshot::channel();
535            let (ready_tx, ready_rx) = oneshot::channel::<()>();
536            let (handle_tx, handle_rx) = mpsc::channel::<Weak<ReactorInner>>();
537
538            let join_handle = thread::Builder::new()
539                .name("dolang-winterop-apc".into())
540                .spawn(move || {
541                    // Signal that we are actually executing our own code before
542                    // doing anything else. A freshly created Windows thread can
543                    // still be inside the OS's own thread-startup sequence
544                    // (loader/CRT init) for a little while after `CreateThread`
545                    // returns a valid, already-usable handle; delivering a
546                    // `QueueUserAPC` to it during that window races that
547                    // startup and can corrupt it. Once *any* of our own code
548                    // has run, that window is guaranteed to be over, so the
549                    // spawning thread waits for this signal before it (or
550                    // anyone else) is allowed to post anything to us.
551                    let _ = ready_tx.send(());
552
553                    // Wait for a weak reference to the shared state, sent by
554                    // the spawning thread right after this thread was created
555                    // (see below — it can only be produced once the OS thread
556                    // exists, since it wraps this thread's own duplicated
557                    // handle). If the sender was dropped instead (spawning
558                    // failed after this thread was already created), just exit
559                    // without ever entering the alertable wait.
560                    let Ok(weak_inner) = handle_rx.recv() else {
561                        return;
562                    };
563                    run(weak_inner);
564                    let _ = exit_tx.send(());
565                })
566                .map_err(io::Error::other)?;
567
568            if ready_rx.await.is_err() {
569                return Err(io::Error::other("apc reactor thread failed to start"));
570            }
571
572            // Duplicate a handle to the new thread that we own independent of
573            // the `JoinHandle` — we never block-join the OS thread (`join()`
574            // instead awaits `exit_rx`, signaled right before the thread's
575            // closure returns), and detach it below.
576            let mut dup: HANDLE = ptr::null_mut();
577            // SAFETY: `join_handle.as_raw_handle()` is a valid, currently-open
578            // thread handle for the thread we just spawned.
579            let ok = unsafe {
580                DuplicateHandle(
581                    GetCurrentProcess(),
582                    join_handle.as_raw_handle() as HANDLE,
583                    GetCurrentProcess(),
584                    &mut dup,
585                    0,
586                    0,
587                    DUPLICATE_SAME_ACCESS,
588                )
589            };
590            if ok == 0 {
591                let err = io::Error::last_os_error();
592                // Unblock the thread (it's parked on `handle_rx.recv()`) so it
593                // exits immediately instead of waiting forever.
594                drop(handle_tx);
595                return Err(err);
596            }
597            // SAFETY: `dup` is a valid, uniquely-owned handle from a successful
598            // `DuplicateHandle` call above.
599            let thread_handle = unsafe { OwnedHandle::from_raw_handle(dup as _) };
600
601            // Detach: dropping a `JoinHandle` without joining it just forfeits
602            // the ability to block-join or observe a panic through it; the OS
603            // thread keeps running independently, driven from here on by our
604            // duplicated `thread_handle`.
605            drop(join_handle);
606
607            let inner = Arc::new(ReactorInner {
608                state: Mutex::new(ReactorState {
609                    thread_handle,
610                    closed: false,
611                }),
612                next_id: AtomicU64::new(0),
613            });
614
615            // The receive end can only fail if the thread already exited
616            // (e.g. it panicked before reaching `handle_rx.recv()`), in which
617            // case there's nothing more to do — `exit_rx` will observe that on
618            // its own once `Control` gets used.
619            let _ = handle_tx.send(Arc::downgrade(&inner));
620
621            Ok((
622                Reactor {
623                    inner: inner.clone(),
624                },
625                Control { inner, exit_rx },
626            ))
627        }
628        #[cfg(all(docsrs, not(windows)))]
629        unreachable!()
630    }
631
632    /// Submits `f` to run on the reactor thread, returning a future for its
633    /// result.
634    ///
635    /// `f` receives a [`Context`] for cooperative cancellation
636    /// ([`Context::cancel_guard`]) and task self-submission
637    /// ([`Context::submit`]).
638    ///
639    /// Fails with [`Closed`] once [`Control::close`] has been called.
640    pub fn submit<T, F>(&self, f: F) -> Result<Task<T>, Closed>
641    where
642        T: Send + 'static,
643        F: AsyncFnOnce(&mut Context) -> T + Send + 'static,
644    {
645        #[cfg(windows)]
646        {
647            // `closed` is read here, separately from the `post` call below, on
648            // purpose: a submission that narrowly beats `cancel` (checks
649            // `closed` just before it's set) isn't a bug — its task-insertion
650            // APC simply shows up during the reactor's flush-and-recheck (see
651            // `run`), which correctly aborts the exit rather than dropping it.
652            if self.inner.state.lock().unwrap().closed {
653                return Err(Closed);
654            }
655
656            let id = TaskId(self.inner.next_id.fetch_add(1, Ordering::Relaxed));
657            let (result_tx, result_rx) = oneshot::channel();
658
659            let task_inner = self.inner.clone();
660            let posted = post(&self.inner, move || {
661                // Only construct (and box) the task's future once actually
662                // running on the reactor thread — see `BoxedTask`'s doc comment
663                // for why that matters.
664                let task: BoxedTask = Box::pin(async move {
665                    let mut ctx = Context {
666                        id,
667                        inner: task_inner,
668                    };
669                    let value = f(&mut ctx).await;
670                    let _ = result_tx.send(value);
671                });
672                REGISTRY.with(|r| {
673                    r.borrow_mut().tasks.insert(
674                        id,
675                        TaskSlot {
676                            future: Some(task),
677                            in_guard: false,
678                            guard_signal: None,
679                        },
680                    );
681                });
682            });
683
684            match posted {
685                Ok(()) => Ok(Task {
686                    id: Some(id),
687                    rx: result_rx,
688                    inner: self.inner.clone(),
689                    _pin: std::marker::PhantomPinned,
690                }),
691                Err(_) => Err(Closed),
692            }
693        }
694        #[cfg(all(docsrs, not(windows)))]
695        {
696            let _ = f;
697            unreachable!()
698        }
699    }
700}
701
702impl Control {
703    /// Stops accepting new [`Reactor::submit`] calls on every clone of the
704    /// corresponding [`Reactor`].
705    pub fn close(&self) {
706        #[cfg(windows)]
707        {
708            // `closed` is set and the wake is posted in the *same* critical
709            // section — not two separate calls — because the reactor thread's
710            // own "read `closed`, and if true, start exiting" step (`run`)
711            // takes the same lock. Without that, the reactor could observe a
712            // stale `closed == false` via some unrelated wake, decide to keep
713            // looping, and go back to sleep with nothing left to ever wake it
714            // again before this call's own post — permanently hanging a
715            // reactor that had nothing else going on.
716            close_reactor(&self.inner);
717        }
718        #[cfg(all(docsrs, not(windows)))]
719        unreachable!()
720    }
721
722    /// Returns a future which awaits the reactor thread's exit.
723    ///
724    /// The reactor thread will not exit until all work completes and no more can be submitted. This
725    /// means that if [`close`](Self::close) is not called first, this function implicitly waits for
726    /// every `Reactor` clone to be dropped in addition to all work completing. [`Join::close`] on
727    /// the returned join handle can be used to make a late-binding decision to close the reactor.
728    pub fn join(self) -> Join {
729        #[cfg(windows)]
730        {
731            let Control { inner, exit_rx } = self;
732            let weak_inner = Arc::downgrade(&inner);
733            drop(inner);
734            Join {
735                inner: Some(weak_inner),
736                exit_rx,
737                _pin: std::marker::PhantomPinned,
738            }
739        }
740        #[cfg(all(docsrs, not(windows)))]
741        unreachable!()
742    }
743}
744
745impl Join {
746    /// Closes the reactor while continuing to await its exit.
747    pub fn close(self: std::pin::Pin<&mut Self>) {
748        #[cfg(windows)]
749        {
750            // SAFETY: `inner` is only mutated in place and is not structurally
751            // pinned.
752            let this = unsafe { self.get_unchecked_mut() };
753            if let Some(inner) = this.inner.take().and_then(|inner| inner.upgrade()) {
754                close_reactor(&inner);
755            }
756        }
757        #[cfg(all(docsrs, not(windows)))]
758        unreachable!()
759    }
760}
761
762#[cfg(windows)]
763impl Future for Join {
764    type Output = ();
765
766    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
767        // SAFETY: we do not move any field out of the pinned join future, and
768        // `exit_rx` is `Unpin`.
769        let this = unsafe { self.get_unchecked_mut() };
770        Pin::new(&mut this.exit_rx).poll(cx).map(|_| ())
771    }
772}
773
774#[cfg(all(docsrs, not(windows)))]
775impl std::future::Future for Join {
776    type Output = ();
777
778    fn poll(
779        self: std::pin::Pin<&mut Self>,
780        cx: &mut std::task::Context<'_>,
781    ) -> std::task::Poll<Self::Output> {
782        let _ = (self, cx);
783        std::task::Poll::Pending
784    }
785}
786
787impl Context {
788    /// Runs `f` and awaits its future, converting a cancellation request for this task into
789    /// `Err(Canceled)`, with `f`'s future dropped instead of the task future.  The surrounding task
790    /// can run async cleanup before finishing.
791    ///
792    /// Only one `cancel_guard` may be active for a task at a time — calling this re-entrantly
793    /// panics.
794    pub async fn cancel_guard<T, F>(&mut self, f: F) -> Result<T, Canceled>
795    where
796        F: AsyncFnOnce(&mut Context) -> T,
797    {
798        #[cfg(windows)]
799        {
800            let id = self.id;
801            let (tx, rx) = oneshot::channel::<()>();
802
803            REGISTRY.with(|r| {
804                let mut reg = r.borrow_mut();
805                let slot = reg
806                    .tasks
807                    .get_mut(&id)
808                    .expect("cancel_guard: task slot missing for the currently running task");
809                assert!(
810                    !slot.in_guard,
811                    "cancel_guard: already inside a guard for this task"
812                );
813                slot.in_guard = true;
814                slot.guard_signal = Some(tx);
815            });
816
817            struct Reset(TaskId);
818            impl Drop for Reset {
819                fn drop(&mut self) {
820                    REGISTRY.with(|r| {
821                        if let Some(slot) = r.borrow_mut().tasks.get_mut(&self.0) {
822                            slot.in_guard = false;
823                            slot.guard_signal = None;
824                        }
825                    });
826                }
827            }
828            let _reset = Reset(id);
829
830            let fut = f(&mut *self);
831            futures::pin_mut!(fut);
832            match future::select(fut, rx).await {
833                Either::Left((value, _)) => Ok(value),
834                Either::Right(_) => Err(Canceled),
835            }
836        }
837        #[cfg(all(docsrs, not(windows)))]
838        {
839            let _ = f;
840            unreachable!()
841        }
842    }
843
844    /// Submits `f` to the reactor thread as a new task and returns a
845    /// future for its result.
846    ///
847    /// Because APCs are queued in a FIFO manner, self-submitting and awaiting a
848    /// task can be used to guarantee that all previously pending APCs, including
849    /// those generated by Win32 APIs, have run.
850    pub fn submit<T, F>(&self, f: F) -> Task<T>
851    where
852        T: Send + 'static,
853        F: AsyncFnOnce(&mut Context) -> T + Send + 'static,
854    {
855        #[cfg(windows)]
856        {
857            let id = TaskId(self.inner.next_id.fetch_add(1, Ordering::Relaxed));
858            let (result_tx, result_rx) = oneshot::channel();
859            let task_inner = self.inner.clone();
860
861            // SAFETY: `Context` methods only ever run from within a task's
862            // poll, which only ever happens on the reactor thread — so
863            // `GetCurrentThread()`'s pseudo-handle correctly refers to it.
864            let result = unsafe {
865                queue_apc(GetCurrentThread(), move || {
866                    let task: BoxedTask = Box::pin(async move {
867                        let mut ctx = Context {
868                            id,
869                            inner: task_inner,
870                        };
871                        let value = f(&mut ctx).await;
872                        let _ = result_tx.send(value);
873                    });
874                    REGISTRY.with(|r| {
875                        r.borrow_mut().tasks.insert(
876                            id,
877                            TaskSlot {
878                                future: Some(task),
879                                in_guard: false,
880                                guard_signal: None,
881                            },
882                        );
883                    });
884                })
885            };
886            result.expect("submitting to the live APC reactor thread should succeed");
887
888            Task {
889                id: Some(id),
890                rx: result_rx,
891                inner: self.inner.clone(),
892                _pin: std::marker::PhantomPinned,
893            }
894        }
895        #[cfg(all(docsrs, not(windows)))]
896        {
897            let _ = f;
898            unreachable!()
899        }
900    }
901}
902
903#[cfg(windows)]
904impl<T> Future for Task<T> {
905    type Output = Result<T, TaskCanceled>;
906
907    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
908        // SAFETY: we do not move any field out of the pinned task. `rx` is
909        // `Unpin`, and `id` is only mutated in place.
910        let this = unsafe { self.get_unchecked_mut() };
911        match Pin::new(&mut this.rx).poll(cx) {
912            Poll::Pending => Poll::Pending,
913            Poll::Ready(result) => {
914                this.id = None;
915                Poll::Ready(result.map_err(|_| TaskCanceled))
916            }
917        }
918    }
919}
920
921#[cfg(all(docsrs, not(windows)))]
922impl<T> std::future::Future for Task<T> {
923    type Output = Result<T, TaskCanceled>;
924
925    fn poll(
926        self: std::pin::Pin<&mut Self>,
927        cx: &mut std::task::Context<'_>,
928    ) -> std::task::Poll<Self::Output> {
929        let _ = (self, cx);
930        std::task::Poll::Pending
931    }
932}
933
934impl<T> Task<T> {
935    /// Requests cancellation without dropping the task.
936    ///
937    /// The task can be awaited afterward to join it. An unguarded task
938    /// resolves with [`TaskCanceled`]; a task inside
939    /// [`Context::cancel_guard`] can run cooperative cleanup and produce its
940    /// normal result instead.
941    pub fn cancel(self: std::pin::Pin<&mut Self>) {
942        #[cfg(windows)]
943        {
944            // SAFETY: cancellation only mutates `id`, which is not structurally
945            // pinned.
946            let this = unsafe { self.get_unchecked_mut() };
947            let Some(id) = this.id.take() else {
948                return;
949            };
950            // Best effort: failure means the reactor thread (and thus the
951            // task) has already gone away, so there is nothing to cancel.
952            let _ = post(&this.inner, move || {
953                let removed = REGISTRY.with(|r| {
954                    let mut reg = r.borrow_mut();
955                    match reg.tasks.get_mut(&id) {
956                        None => None,
957                        Some(slot) if slot.in_guard => {
958                            if let Some(tx) = slot.guard_signal.take() {
959                                let _ = tx.send(());
960                            }
961                            None
962                        }
963                        Some(_) => reg.tasks.remove(&id),
964                    }
965                });
966                // Drop outside the RefCell borrow above, in case the future's
967                // own teardown happens to touch the registry.
968                drop(removed);
969            });
970        }
971        #[cfg(all(docsrs, not(windows)))]
972        unreachable!()
973    }
974}
975
976#[cfg(windows)]
977impl<T> Drop for Task<T> {
978    fn drop(&mut self) {
979        // SAFETY: a value is pinned in place for the duration of its destructor.
980        unsafe { Pin::new_unchecked(self) }.cancel();
981    }
982}
983
984#[cfg(all(docsrs, not(windows)))]
985impl<T> Drop for Task<T> {
986    fn drop(&mut self) {}
987}
988
989#[cfg(all(windows, test))]
990mod tests {
991    use std::{sync::mpsc, time::Duration};
992
993    use windows_sys::Win32::System::Threading::GetCurrentThreadId;
994
995    use super::*;
996
997    /// Sends on `tx` when dropped — lets a test observe exactly when a
998    /// task's future was actually torn down.
999    struct DropSignal(Option<mpsc::Sender<()>>);
1000
1001    impl Drop for DropSignal {
1002        fn drop(&mut self) {
1003            if let Some(tx) = self.0.take() {
1004                let _ = tx.send(());
1005            }
1006        }
1007    }
1008
1009    const TIMEOUT: Duration = Duration::from_secs(5);
1010
1011    /// Joins `control` on a helper thread with a bounded wait, so a bug
1012    /// that makes `join()` hang doesn't hang the whole test suite.
1013    fn join_with_timeout(control: Control) {
1014        let (tx, rx) = mpsc::channel();
1015        thread::spawn(move || {
1016            futures::executor::block_on(control.join());
1017            let _ = tx.send(());
1018        });
1019        rx.recv_timeout(TIMEOUT)
1020            .expect("reactor did not shut down in time");
1021    }
1022
1023    /// Runs `fut` to completion on a helper thread with a bounded wait, so a
1024    /// bug that stalls the reactor doesn't hang the whole test suite.
1025    fn block_on_with_timeout<T: Send + 'static>(
1026        fut: impl Future<Output = T> + Send + 'static,
1027    ) -> T {
1028        let (tx, rx) = mpsc::channel();
1029        thread::spawn(move || {
1030            let _ = tx.send(futures::executor::block_on(fut));
1031        });
1032        rx.recv_timeout(TIMEOUT)
1033            .expect("future did not resolve in time")
1034    }
1035
1036    #[test]
1037    fn submit_and_await_result() {
1038        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1039        let task = reactor.submit(async move |_| 42).unwrap();
1040        assert_eq!(block_on_with_timeout(task).unwrap(), 42);
1041        control.close();
1042        join_with_timeout(control);
1043    }
1044
1045    #[test]
1046    fn dropping_unguarded_task_force_drops_it() {
1047        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1048        let (started_tx, started_rx) = mpsc::channel();
1049        let (tx, rx) = mpsc::channel();
1050        let task = reactor
1051            .submit(async move |_| {
1052                let _signal = DropSignal(Some(tx));
1053                let _ = started_tx.send(());
1054                future::pending::<()>().await
1055            })
1056            .unwrap();
1057
1058        // Wait for the task to actually start running (and reach the
1059        // pending await) before cancelling it — otherwise we'd just be
1060        // testing that dropping a never-polled task drops it, which is a
1061        // trivially different (and trivially true) case.
1062        started_rx
1063            .recv_timeout(TIMEOUT)
1064            .expect("task should have started running");
1065        drop(task);
1066
1067        rx.recv_timeout(TIMEOUT)
1068            .expect("unguarded task should be force-dropped promptly");
1069        control.close();
1070        join_with_timeout(control);
1071    }
1072
1073    #[test]
1074    fn cancel_and_await_unguarded_task() {
1075        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1076        let (started_tx, started_rx) = mpsc::channel();
1077        let task = reactor
1078            .submit(async move |_| {
1079                let _ = started_tx.send(());
1080                future::pending::<()>().await
1081            })
1082            .unwrap();
1083
1084        started_rx
1085            .recv_timeout(TIMEOUT)
1086            .expect("task should have started running");
1087        let mut task = Box::pin(task);
1088        task.as_mut().cancel();
1089        task.as_mut().cancel();
1090        assert_eq!(block_on_with_timeout(task), Err(TaskCanceled));
1091
1092        control.close();
1093        join_with_timeout(control);
1094    }
1095
1096    #[test]
1097    fn dropping_guarded_task_runs_cooperative_cleanup() {
1098        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1099        let (started_tx, started_rx) = mpsc::channel();
1100        let (tx, rx) = mpsc::channel();
1101        let task = reactor
1102            .submit(async move |ctx| {
1103                let result = ctx
1104                    .cancel_guard(async move |_| {
1105                        let _ = started_tx.send(());
1106                        future::pending::<()>().await
1107                    })
1108                    .await;
1109                assert!(result.is_err(), "expected Canceled");
1110                let _ = tx.send(());
1111            })
1112            .unwrap();
1113
1114        // Wait for the task to actually enter its guard before cancelling
1115        // it, so this exercises the cooperative path rather than racing a
1116        // force-drop against the task's very first poll.
1117        started_rx
1118            .recv_timeout(TIMEOUT)
1119            .expect("task should have entered its guard");
1120        drop(task);
1121
1122        rx.recv_timeout(TIMEOUT)
1123            .expect("guarded task should observe cancellation and clean up cooperatively");
1124        control.close();
1125        join_with_timeout(control);
1126    }
1127
1128    #[test]
1129    fn close_rejects_new_submissions() {
1130        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1131        control.close();
1132
1133        let result = reactor.submit(async move |_| ());
1134        assert_eq!(result.err(), Some(Closed));
1135
1136        join_with_timeout(control);
1137    }
1138
1139    #[test]
1140    fn join_resolves_after_mixed_tasks_are_cancelled() {
1141        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1142
1143        let guarded = reactor
1144            .submit(async move |ctx| {
1145                let _ = ctx
1146                    .cancel_guard(async move |_| future::pending::<()>().await)
1147                    .await;
1148            })
1149            .unwrap();
1150        let plain = reactor
1151            .submit(async move |_| future::pending::<()>().await)
1152            .unwrap();
1153
1154        drop(guarded);
1155        drop(plain);
1156        control.close();
1157
1158        // If this returns at all, `join()` correctly observed both
1159        // cancellations and drained the registry.
1160        join_with_timeout(control);
1161    }
1162
1163    #[test]
1164    fn join_resolves_without_close_once_every_handle_is_dropped() {
1165        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1166
1167        let task = reactor
1168            .submit(async move |_| future::pending::<()>().await)
1169            .unwrap();
1170
1171        // Drop every `Reactor` clone and live task, but never call
1172        // `cancel()`. `join()` should still resolve — it drops its own
1173        // reference and waits, so this exercises the reactor noticing that
1174        // *nothing* references it anymore (not just that it was told to
1175        // close) and exiting on its own.
1176        drop(task);
1177        drop(reactor);
1178
1179        join_with_timeout(control);
1180    }
1181
1182    #[test]
1183    fn pending_join_can_close_reactor() {
1184        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1185        let mut join = Box::pin(control.join());
1186
1187        let waker = futures::task::noop_waker();
1188        let mut cx = task::Context::from_waker(&waker);
1189        assert!(matches!(join.as_mut().poll(&mut cx), Poll::Pending));
1190        join.as_mut().close();
1191        block_on_with_timeout(join);
1192
1193        assert_eq!(reactor.submit(async |_| ()).err(), Some(Closed));
1194    }
1195
1196    #[test]
1197    fn context_submit_runs_on_reactor_thread() {
1198        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1199        let (tx, rx) = mpsc::channel();
1200        let task = reactor
1201            .submit(async move |ctx| {
1202                let reactor_tid = unsafe { GetCurrentThreadId() };
1203                let posted_tid = ctx
1204                    .submit(async |_| unsafe { GetCurrentThreadId() })
1205                    .await
1206                    .unwrap();
1207                let _ = tx.send((reactor_tid, posted_tid));
1208            })
1209            .unwrap();
1210
1211        let (reactor_tid, posted_tid) = rx.recv_timeout(TIMEOUT).unwrap();
1212        assert_eq!(reactor_tid, posted_tid);
1213
1214        drop(task);
1215        control.close();
1216        join_with_timeout(control);
1217    }
1218
1219    #[test]
1220    fn panic_in_one_task_does_not_affect_others() {
1221        let (reactor, control) = futures::executor::block_on(Reactor::new()).unwrap();
1222
1223        // Suppress the default panic hook's stderr output for this
1224        // deliberately-triggered, caught panic.
1225        let previous_hook = std::panic::take_hook();
1226        std::panic::set_hook(Box::new(|_| {}));
1227        let panicking = reactor
1228            .submit(async move |_| {
1229                panic!("intentional test panic");
1230            })
1231            .unwrap();
1232        let panicking_result = block_on_with_timeout(panicking);
1233        std::panic::set_hook(previous_hook);
1234        assert!(panicking_result.is_err());
1235
1236        let ok = reactor.submit(async move |_| 7).unwrap();
1237        assert_eq!(block_on_with_timeout(ok).unwrap(), 7);
1238
1239        control.close();
1240        join_with_timeout(control);
1241    }
1242}