Asynchronous Rust

Wakers and Executors

An executor is the runtime scheduler that polls top-level Future tasks, and a Waker (std::task::Waker, passed inside Context<'_>) is the callback handle that a pending future uses to notify the executor that the task is ready to be polled again.

How Executors, Futures, Reactors, and Wakers Interact

This separation of concerns is what allows any async library in the Rust ecosystem to run on any executor without tight coupling:

+-----------------------------------+        +-----------------------------------+
|  Executor (Task Scheduler)        |        |  Reactor / Timer / Worker Thread  |
|  - Pops task from `ready_queue`   |        |  - Monitors OS epoll/kqueue/timers|
|  - Creates `Waker` for task       |        +-----------------+-----------------+
|  - Calls `task.poll(&mut cx)` ----+---+                      ^
+-----------------------------------+   |                      |
                  ^                     v                      |
                  |            [ Leaf Future ]                 |
                  |            Returns `Poll::Pending` &       |
                  |            passes `cx.waker().clone()` ----+
                  |                                            |
                  +---- Calls `waker.wake()` when event fires -+
                        (Pushes task back onto `ready_queue`!)

Simplified Custom Waker Creation with std::task::Wake

Historically, constructing a Waker required manually building a raw pointer vtable (RawWakerVTable). Stabilized in the standard library, the std::task::Wake trait implements From<Arc<W>> for Waker for any W: Wake + Send + Sync + 'static—allowing you to build a complete, working single-threaded or multi-threaded executor in ~40 lines of 100% safe Rust!

use std::future::Future;
use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll, Wake, Waker};
use std::thread::{self, Thread};

// Custom Waker backed by `std::thread::Thread::unpark`:
struct ThreadWaker {
    thread: Thread,
    wake_count: AtomicUsize,
}

impl Wake for ThreadWaker {
    fn wake(self: Arc<Self>) {
        self.wake_by_ref();
    }

    fn wake_by_ref(self: &Arc<Self>) {
        self.wake_count.fetch_add(1, Ordering::Release);
        self.thread.unpark();
    }
}

// A minimal `block_on` executor built in 100% Safe Rust:
pub fn block_on<F: Future>(fut: F) -> (F::Output, usize) {
    let mut pinned = pin!(fut);
    let waker_state = Arc::new(ThreadWaker {
        thread: thread::current(),
        wake_count: AtomicUsize::new(0),
    });

    // Convert `Arc<ThreadWaker>` into a standard `std::task::Waker`:
    let waker: Waker = Waker::from(Arc::clone(&waker_state));
    let mut cx = Context::from_waker(&waker);

    loop {
        if let Poll::Ready(output) = pinned.as_mut().poll(&mut cx) {
            return (output, waker_state.wake_count.load(Ordering::Acquire));
        }
        // Sleep until a background event calls `waker.wake()`:
        thread::park();
    }
}

fn main() {
    let (result, wakes) = block_on(async {
        let mut polled_once = false;
        std::future::poll_fn(|cx| {
            if polled_once {
                Poll::Ready("completed after background wake!")
            } else {
                polled_once = true;
                // Spawn a background thread that wakes our custom `Waker`:
                let waker = cx.waker().clone();
                thread::spawn(move || {
                    waker.wake();
                });
                Poll::Pending
            }
        })
        .await
    });

    println!("result = {:?}, wakes recorded = {}", result, wakes);
}
result = "completed after background wake!", wakes recorded = 1