In a single-threaded application, does calling the waker poll all futures, only futures which last returned Poll::NotReady, or does it only poll the future which is associated with the context.waker() call?
Also, if I needed to make an async callback, would it be "best" to:
impl Future or Stream for a struct
in the impl, make sure to store the waker inside the struct using the context
call poll on the struct once to load the waker in the struct
from the closure which created the struct, grab the waker and store it in a hashmap to be called once relevant data (or a timeout) occurs
Your question is rather broad and depends on actual runtime implementation.
But normally waker is associated with runtime context which is likely to be unique to single runtime. (In case of multi-threaded tokio it might be a bit more complicated)
In general runtime should poll task that invoked wake only.
But it is really up to particular runtime implementation.
async callback
You really need to elaborate what you want to achieve.
This is rather unclear term you use here
Part of the purpose of having an "async callback" is to send a packet with a particular ID outbound, and then await a response from the other end. The future will return Poll::NotReady when it is first polled, because no response has yet arrived. Then, it would be the duty of an async socket-listener to awaken the future and poll it to continue the future. I would also like the future to timeout after some time, so I'd have to wrap the send_packet_fn in a Timeout struct as provided by tokio
The contract for Future::poll is that it returns “quickly” in all cases and, if it returns Pending, it has made some kind of arrangement for the waker to be triggered when it can make progress. In particular, you need to correctly handle poll being called again before the waker has been activated.
Usually, whatever code is actively watching for incoming events will have a queue of wakers to notify when an event comes in, and the Future struct will have a way to enqueue any waker it’s given via poll.
If you’re just waiting for other Futures to be ready, you can rely on their poll implementations registering the waker wherever it’s needed.
In short, each wakers are associated to some task.
Futures are meant to be combined to form bigger future. When we stop combining futures and pass it to runtime via api like tokio::spawn(), now it's called task. You can think tasks as asynchronous threads. Tasks usually be executed within fixed amount of OS threads, either in parallel or in serial though concurrently, similar to how OS threads are executed within fixed amount of CPU cores.