Waker

When the executor polls a future, it returns Poll::Pending if it's blocked by another operation, i.e. waiting for the kernel to finish reading from disk. The question is when should the executor poll again?

A dumb solution is to have the executor periodically poll the Future to check if it's ready yet. But that’s inefficient and wastes CPU cycles.

Instead, a more efficient solution is to pass a callback to the Future and have the Future invoke the callback when it is unblocked. This is what the Waker is for.

The Waker is passed to the Future each time it's polled. As a refresher, here is the function signature for poll:

#![allow(unused)]
fn main() {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>
}

Each Context struct contains a waker that can be retrieved with cx.waker(). Each waker has a wake() method, which notifies the executor that the Future is ready to be polled again.

To create a Waker, we can use the from_raw constructor:

#![allow(unused)]
fn main() {
pub const unsafe fn from_raw(waker: RawWaker) -> Waker
}

Each waker has a wake method that can be called when it is done.

Here is an example borrowed from the async book that implements a timer with the waker:

pub struct TimerFuture {
    shared_state: Arc<Mutex<SharedState>>,
}

struct SharedState {
    completed: bool,
    waker: Option<Waker>,
}

impl Future for TimerFuture {
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut shared_state = self.shared_state.lock().unwrap();
        if shared_state.completed {
            Poll::Ready(())
        } else {
            shared_state.waker = Some(cx.waker().clone());
            Poll::Pending
        }
    }
}

impl TimerFuture {
    pub fn new(duration: Duration) -> Self {
        let shared_state = Arc::new(Mutex::new(SharedState {
            completed: false,
            waker: None,
        }));

        // Spawn the new thread
        let thread_shared_state = shared_state.clone();
        thread::spawn(move || {
            thread::sleep(duration);
            let mut shared_state = thread_shared_state.lock().unwrap();
            shared_state.completed = true;
            if let Some(waker) = shared_state.waker.take() {
                waker.wake()
            }
        });

        TimerFuture { shared_state }
    }
}

In this example, a thread is created when the TimerFuture is created. The thread simply performs thread::sleep which acts as the timer. When the future is polled, the waker is stored. When the thread::sleep completes, the waker::wake method is called to notify the poller that it can be polled again.