Does the awaken task synchronize with calling of `wake` within the tokio runtime?

use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::task::Poll::Pending;
use std::task::Poll::Ready;
#[tokio::main]
async fn main() {
    let mut init = false;
    let val = Arc::new(AtomicI32::new(0));
    std::future::poll_fn(|cx| {
        if !init {
            init = true;
            let val2 = val.clone();
            let waker = cx.waker().clone();
            std::thread::spawn(move || {
                std::thread::sleep(std::time::Duration::from_secs(1));
                val2.store(1, Ordering::Relaxed); //#0
                waker.wake(); // #1
            });
            Pending
        } else {
            val.load(Ordering::Relaxed); // #2
            Ready(())
        }
    })
    .await;
}

Does #1 synchronize with the awaken task within the tokio runtime? In other words, the calling wake synchronize with the awaken task such that #2 is guaranteed to read the value written by #0?

I don't find the relevant document about the memory order of wake and awaken task in tokio. Unlike unpark and park, which has clearly document to say what their memory order is

Calls to park synchronize-with calls to unpark, meaning that memory operations performed before a call to unpark are made visible to the thread that consumes the token and returns from park. Note that all park and unpark operations for a given thread form a total order and park synchronizes-with all prior unpark operations.

1 Like

When you call wake, it is guaranteed that poll will be called afterwards, and it's guaranteed that the call to wake happens-before the call to poll.

However! The runtime may also call poll without a wakeup, so it's possible that after 500ms poll gets called. Since the thread is still sleeping, the atomic will not have been modified yet in that case. So your code is wrong due to the possibility of spurious wakeups.

Also, if a spurious poll happens, that call may give you a new waker. To support that, your thread needs to be able to swap out the waker.

Related discussion:

Related article:

4 Likes

When you call wake, it is guaranteed that poll will be called afterwards, and it's guaranteed that the call to wake happens-before the call to poll.

This point seems to not be documented by async runtime in their documents(neither tokio nor async-std, smol, for example)?

To me, this is part of the contract of the future trait. It's not a runtime-specific matter.

The future trait requires that if you call wake, there must be a call to poll afterwards. In general, if something must happen before something else, then that should involve a happens-before relationship unless explicitly documented to behave like relaxed atomics.

So, I am looking forward to a document regarding memory order between wake and the corresponding waken Future like the document for park and unpark. The document is unclear about this point If I didn't miss something.