Skip to content

Commit

Permalink
io: make tokio::io::empty cooperative (#4300)
Browse files Browse the repository at this point in the history
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: #4291
  • Loading branch information
BraulioVM committed Dec 10, 2021
1 parent 0bc9160 commit eb1af7f
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
20 changes: 18 additions & 2 deletions tokio/src/io/util/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,18 @@ impl AsyncRead for Empty {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context<'_>,
cx: &mut Context<'_>,
_: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
ready!(poll_proceed_and_make_progress(cx));
Poll::Ready(Ok(()))
}
}

impl AsyncBufRead for Empty {
#[inline]
fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
ready!(poll_proceed_and_make_progress(cx));
Poll::Ready(Ok(&[]))
}

Expand All @@ -73,6 +75,20 @@ impl fmt::Debug for Empty {
}
}

cfg_coop! {
fn poll_proceed_and_make_progress(cx: &mut Context<'_>) -> Poll<()> {
let coop = ready!(crate::coop::poll_proceed(cx));
coop.made_progress();
Poll::Ready(())
}
}

cfg_not_coop! {
fn poll_proceed_and_make_progress(_: &mut Context<'_>) -> Poll<()> {
Poll::Ready(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
32 changes: 32 additions & 0 deletions tokio/tests/io_util_empty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![cfg(feature = "full")]
use tokio::io::{AsyncBufReadExt, AsyncReadExt};

#[tokio::test]
async fn empty_read_is_cooperative() {
tokio::select! {
biased;

_ = async {
loop {
let mut buf = [0u8; 4096];
let _ = tokio::io::empty().read(&mut buf).await;
}
} => {},
_ = tokio::task::yield_now() => {}
}
}

#[tokio::test]
async fn empty_buf_reads_are_cooperative() {
tokio::select! {
biased;

_ = async {
loop {
let mut buf = String::new();
let _ = tokio::io::empty().read_line(&mut buf).await;
}
} => {},
_ = tokio::task::yield_now() => {}
}
}

0 comments on commit eb1af7f

Please sign in to comment.