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}