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
make tokio::io::empty
cooperative
#4300
make tokio::io::empty
cooperative
#4300
Conversation
Also, seems like you have a warning in the code:
|
tokio/tests/io_util_empty.rs
Outdated
async fn repeated_reads_on_same_empty_are_cooperative() { | ||
// the test would likely hang if reads on `empty` weren't | ||
// cooperative | ||
let _ = timeout(Duration::from_millis(1000), async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These timeouts are too long when you aren't mocking the time driver. Please try reducing them to 5 ms or so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, selecting on the async block and tokio::task::yield_now
would probably work too.
And also, welcome! |
Thanks for all the feedback and all the pointers! The coop system makes a lot of sense. I implemented the changes but I believe they won't fix the original issue! That's because of how See the future implementation for tokio/tokio/src/time/timeout.rs Lines 163 to 183 in ee4b2ed
and tokio/tokio/src/time/driver/sleep.rs Lines 379 to 388 in ee4b2ed
I've observed this behaviour locally after applying the changes. Does this sound reasonable to you? As for next steps:
What are your thoughts? |
Thank you for pointing that out, that explains why some other case I was thinking of also doesn't work. We do want to make Can you update this PR with the changes I requested for If you want to discuss the changes to timeout further before opening a PR, then please open an issue so we can do it there instead of here. |
Sounds like a plan! |
Reads and buffered reads from a `tokio::io::empty` were always marked as ready. That makes sense, given that there is nothing to wait for. However, doing repeated reads on the `empty` could stall the event loop and prevent other tasks from making progress. This change uses tokio's coop system to yield control back to the executor when appropriate. Note that the issue that originally triggered this PR is not fixed yet, because the `timeout` function will not poll the timer after empty::read runs out of budget. A different change will be needed to address that. Refs: tokio-rs#4291
eb7a0a2
to
e92466a
Compare
I will fix the failing build later today |
This means, among other things, that calls to empty::read on a tight loop will stall the executor.
Is there anything I should do before this gets merged? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good.
Up until now, if the future that we were applying a timeout to consistently depleted the coop budget, the timeout never got a chance to be evaluated. In the next call to `poll`, the underlying future would be polled and it would once again deplete the budget. In those circumstances, timeouts would not be respected. This can be surprising to people, and in fact it was in tokio-rs#4291 . The solution is to make a budget exception with `timeout` if it was the underlying future that depleted the budget. Refs: tokio-rs#4291 , tokio-rs#4300
Add coop checks on pipe poll_read and poll_write. Fixes: tokio-rs#4470 Refs: tokio-rs#4291, tokio-rs#4300
Add coop checks on pipe poll_read and poll_write. Fixes: tokio-rs#4470 Refs: tokio-rs#4291, tokio-rs#4300
Add coop checks on pipe poll_read and poll_write. Fixes: tokio-rs#4470 Refs: tokio-rs#4291, tokio-rs#4300
Add coop checks on pipe poll_read and poll_write. Fixes: tokio-rs#4470 Refs: tokio-rs#4291, tokio-rs#4300
Fixes: #4291
Motivation
Reads and buffered reads from a
tokio::io::empty
were always markedas ready. That makes sense, given that there is nothing to wait for.
However, doing repeated reads on the
empty
could stall the eventloop and prevent other tasks from making progress.
Solution
This change makes reads on empty objects cooperative. One of every two
reads will return
Poll::Pending
, which will give the executor achance to keep making progress elsewhere.
Testing
I've added four integration tests. The tests wouldn't fail if the fix hadn't been implemented,
they would just hang forever (just like the original issue).
This is my first contribution to the project, so maybe there are higher-level ways of achieving
this behaviour and testing this functionality. Let me know if that's the case!