Skip to content

Commit eb1af7f

Browse files
authored
io: make tokio::io::empty cooperative (#4300)
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
1 parent 0bc9160 commit eb1af7f

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

tokio/src/io/util/empty.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,18 @@ impl AsyncRead for Empty {
5050
#[inline]
5151
fn poll_read(
5252
self: Pin<&mut Self>,
53-
_: &mut Context<'_>,
53+
cx: &mut Context<'_>,
5454
_: &mut ReadBuf<'_>,
5555
) -> Poll<io::Result<()>> {
56+
ready!(poll_proceed_and_make_progress(cx));
5657
Poll::Ready(Ok(()))
5758
}
5859
}
5960

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

@@ -73,6 +75,20 @@ impl fmt::Debug for Empty {
7375
}
7476
}
7577

78+
cfg_coop! {
79+
fn poll_proceed_and_make_progress(cx: &mut Context<'_>) -> Poll<()> {
80+
let coop = ready!(crate::coop::poll_proceed(cx));
81+
coop.made_progress();
82+
Poll::Ready(())
83+
}
84+
}
85+
86+
cfg_not_coop! {
87+
fn poll_proceed_and_make_progress(_: &mut Context<'_>) -> Poll<()> {
88+
Poll::Ready(())
89+
}
90+
}
91+
7692
#[cfg(test)]
7793
mod tests {
7894
use super::*;

tokio/tests/io_util_empty.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![cfg(feature = "full")]
2+
use tokio::io::{AsyncBufReadExt, AsyncReadExt};
3+
4+
#[tokio::test]
5+
async fn empty_read_is_cooperative() {
6+
tokio::select! {
7+
biased;
8+
9+
_ = async {
10+
loop {
11+
let mut buf = [0u8; 4096];
12+
let _ = tokio::io::empty().read(&mut buf).await;
13+
}
14+
} => {},
15+
_ = tokio::task::yield_now() => {}
16+
}
17+
}
18+
19+
#[tokio::test]
20+
async fn empty_buf_reads_are_cooperative() {
21+
tokio::select! {
22+
biased;
23+
24+
_ = async {
25+
loop {
26+
let mut buf = String::new();
27+
let _ = tokio::io::empty().read_line(&mut buf).await;
28+
}
29+
} => {},
30+
_ = tokio::task::yield_now() => {}
31+
}
32+
}

0 commit comments

Comments
 (0)