mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-08 00:00:13 +02:00
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:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user