Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add yield_now() #43

Closed
ghost opened this issue Apr 28, 2020 · 15 comments
Closed

Add yield_now() #43

ghost opened this issue Apr 28, 2020 · 15 comments
Labels
enhancement New feature or request request for comments Feedback, thoughts, suggestions, opinions

Comments

@ghost
Copy link

ghost commented Apr 28, 2020

Is this something that we need? Thoughts? I guess I'm just looking for someone to say "hey yes let's add this"... :)

@llebout
Copy link
Contributor

llebout commented Apr 28, 2020

What does this do exactly? Allow a task to give up it's own scheduled time to other tasks?

@csmoe
Copy link

csmoe commented Apr 28, 2020

@ghost
Copy link
Author

ghost commented Apr 28, 2020

Yes, it's just a future that returns Pending once and then Ready(()) after that.

@Ekleog
Copy link

Ekleog commented Apr 28, 2020

There's at least one use case for that: tasks that need to busy-loop on a value in memory (shared with another process / hardware device) when there's no way to be notified via epoll or similar when said value changes.

I don't have a use for it right now personally though, but have already seen it, though at times it could have been worked around in other ways.

@ghost ghost added enhancement New feature or request request for comments Feedback, thoughts, suggestions, opinions labels May 3, 2020
@jacobrosenthal
Copy link

Im just repeating what @Ekleog said. Im thinking of how to port something like a single thread block_on polling a sensor like these (non smol) embedded async examples
https://github.com/rust-embedded-community/async-on-embedded/blob/master/nrf52/examples/8-sensor.rs

@jjl
Copy link

jjl commented May 10, 2020

It has just occurred to me that this would be a useful method in the browser, where we're limited to one thread but one might reasonably want to do some long computation.

Not actually sure how to implement yet, but just a thought :)

@ghost
Copy link
Author

ghost commented May 10, 2020

I would actually love to see this function added to futures crate :) Anyone wants to submit a PR there?

@Ekleog
Copy link

Ekleog commented May 10, 2020

Is there actually way to implement it that's correct over all executors? I've just had a look at async-std, and it looks like it requires some runtime-specific operations.

@taiki-e
Copy link
Collaborator

taiki-e commented May 10, 2020

Yes, it's just a future that returns Pending once and then Ready(()) after that.

Looks like very similar to futures::pending! macro (but it does not call .wake_by_ref();)

EDIT: see #43 (comment)

@Ekleog
Copy link

Ekleog commented May 10, 2020

Some executors can have optimizations that make a future that's woken before the executor received the Pending be re-ran automatically without yielding to another future (for cache locality, for instance).

I'd guess that's what async-std does, as well as the reason why it's needing to call home in the yield_now() code.

@laizy
Copy link

laizy commented May 10, 2020

the yield_now behavior depends on executor. there was a pr rust-lang/futures-rs#1430 to add yield_now to futures-rs, but not merged.

@ghost
Copy link
Author

ghost commented May 10, 2020

@Ekleog The only reason why async-std uses a thread-local flag for yielding is because async-task at the time didn't have an efficient way to checking whether a task has been re-scheduled while running (i.e. whether it has yielded).

In async-task v0.3.0, Task::run() returns a bool indicating whether the task was rescheduled. If so, we push it into the back of the queue instead of into the slot. That's it.

Yielding really doesn't need to be executor-dependent. It was just a silly hack in async-std that is now going away :)

@ghost
Copy link
Author

ghost commented May 10, 2020

Just to elaborate: if a scheduler doesn't handle yield_now().await correctly, it's broken anyway.

A scheduler that can't yield correctly on its own suffers from starvation issues because if a task wakes itself for whatever reason, it might never give other tasks a chance to run. async-std got around this starvation problem by flushing the slot every now and then, but it's not an ideal solution.

So the argument that this is executor-dependent doesn't hold, in my opinion. I read the previous discussion in the futures repository and am confused about why it got closed. Perhaps restarting a discussion on it today would have a different result.

@Ekleog
Copy link

Ekleog commented May 10, 2020

A scheduler could handle its own yield_now().await, while still not supporting an executor-generic yield_now().await.

Let us assume an executor tuned specially for throughput, without caring much about latency. Starvation is not that big a deal in such an executor, but cache locality very much is. So, if this executor sees a future that returns Pending yet is still wake-able, it'll just re-run it straight away, to take advantage of all the hot cache.

Such an executor could still support yield_now().await for use cases that require some kind of fairness: it'd just return Pending, wake the task immediately, but also set a flag saying to requeue the task behind the other waiting tasks.

Thus for such an executor, a generic yield_now() would be a no-op, yet an executor-specific yield_now() would work without any issue.

@ghost
Copy link
Author

ghost commented Jul 28, 2020

We now have smol::future::yield_now().

@ghost ghost closed this as completed Jul 28, 2020
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request request for comments Feedback, thoughts, suggestions, opinions
Development

No branches or pull requests

7 participants