rt: yield_now defers task until after driver poll (#5223)

Previously, calling `task::yield_now().await` would yield the current
task to the scheduler, but the scheduler would poll it again before
polling the resource drivers. This behavior can result in starving the
resource drivers.

This patch creates a queue tracking yielded tasks. The scheduler
notifies those tasks **after** polling the resource drivers.

Refs: #5209
This commit is contained in:
Carl Lerche
2022-11-30 14:21:08 -08:00
committed by GitHub
parent 993a60b7c7
commit 22862739dd
11 changed files with 240 additions and 7 deletions
+78 -1
View File
@@ -9,6 +9,9 @@ macro_rules! rt_test {
mod current_thread_scheduler {
$($t)*
#[cfg(not(target_os="wasi"))]
const NUM_WORKERS: usize = 1;
fn rt() -> Arc<Runtime> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -22,6 +25,8 @@ macro_rules! rt_test {
mod threaded_scheduler_4_threads {
$($t)*
const NUM_WORKERS: usize = 4;
fn rt() -> Arc<Runtime> {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
@@ -36,6 +41,8 @@ macro_rules! rt_test {
mod threaded_scheduler_1_thread {
$($t)*
const NUM_WORKERS: usize = 1;
fn rt() -> Arc<Runtime> {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
@@ -652,7 +659,12 @@ rt_test! {
for _ in 0..100 {
rt.spawn(async {
loop {
tokio::task::yield_now().await;
// Don't use Tokio's `yield_now()` to avoid special defer
// logic.
let _: () = futures::future::poll_fn(|cx| {
cx.waker().wake_by_ref();
std::task::Poll::Pending
}).await;
}
});
}
@@ -680,6 +692,71 @@ rt_test! {
});
}
/// Tests that yielded tasks are not scheduled until **after** resource
/// drivers are polled.
///
/// Note: we may have to delete this test as it is not necessarily reliable.
/// The OS does not guarantee when I/O events are delivered, so there may be
/// more yields than anticipated.
#[test]
#[cfg(not(target_os="wasi"))]
fn yield_defers_until_park() {
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::Barrier;
let rt = rt();
let flag = Arc::new(AtomicBool::new(false));
let barrier = Arc::new(Barrier::new(NUM_WORKERS));
rt.block_on(async {
// Make sure other workers cannot steal tasks
#[allow(clippy::reversed_empty_ranges)]
for _ in 0..(NUM_WORKERS-1) {
let flag = flag.clone();
let barrier = barrier.clone();
tokio::spawn(async move {
barrier.wait();
while !flag.load(SeqCst) {
std::thread::sleep(std::time::Duration::from_millis(1));
}
});
}
barrier.wait();
tokio::spawn(async move {
// Create a TCP litener
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::join!(
async {
// Done blocking intentionally
let _socket = std::net::TcpStream::connect(addr).unwrap();
// Yield until connected
let mut cnt = 0;
while !flag.load(SeqCst){
tokio::task::yield_now().await;
cnt += 1;
if cnt >= 10 {
panic!("yielded too many times; TODO: delete this test?");
}
}
},
async {
let _ = listener.accept().await.unwrap();
flag.store(true, SeqCst);
}
);
}).await.unwrap();
});
}
#[cfg(not(target_os="wasi"))] // Wasi does not support threads
#[test]
fn client_server_block_on() {