Skip to content

Commit b023522

Browse files
authored
rt: add unstable option to disable the LIFO slot (#4936)
The multi-threaded scheduler includes a per-worker LIFO slot to store the last scheduled task. This can improve certain usage patterns, especially message passing between tasks. However, this LIFO slot is not currently stealable. Eventually, the LIFO slot **will** become stealable. However, as a stop-gap, this unstable option lets users disable the LIFO task when doing so improves their application's overall performance. Refs: #4941
1 parent 733931d commit b023522

File tree

4 files changed

+85
-1
lines changed

4 files changed

+85
-1
lines changed

tokio/src/runtime/builder.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ pub struct Builder {
8585
/// How many ticks before yielding to the driver for timer and I/O events?
8686
pub(super) event_interval: u32,
8787

88+
/// When true, the multi-threade scheduler LIFO slot should not be used.
89+
///
90+
/// This option should only be exposed as unstable.
91+
pub(super) disable_lifo_slot: bool,
92+
8893
#[cfg(tokio_unstable)]
8994
pub(super) unhandled_panic: UnhandledPanic,
9095
}
@@ -252,6 +257,8 @@ impl Builder {
252257

253258
#[cfg(tokio_unstable)]
254259
unhandled_panic: UnhandledPanic::Ignore,
260+
261+
disable_lifo_slot: false,
255262
}
256263
}
257264

@@ -781,6 +788,47 @@ impl Builder {
781788
self.unhandled_panic = behavior;
782789
self
783790
}
791+
792+
/// Disables the LIFO task scheduler heuristic.
793+
///
794+
/// The multi-threaded scheduler includes a heuristic for optimizing
795+
/// message-passing patterns. This heuristic results in the **last**
796+
/// scheduled task being polled first.
797+
///
798+
/// To implement this heuristic, each worker thread has a slot which
799+
/// holds the task that should be polled next. However, this slot cannot
800+
/// be stolen by other worker threads, which can result in lower total
801+
/// throughput when tasks tend to have longer poll times.
802+
///
803+
/// This configuration option will disable this heuristic resulting in
804+
/// all scheduled tasks being pushed into the worker-local queue, which
805+
/// is stealable.
806+
///
807+
/// Consider trying this option when the task "scheduled" time is high
808+
/// but the runtime is underutilized. Use tokio-rs/tokio-metrics to
809+
/// collect this data.
810+
///
811+
/// # Unstable
812+
///
813+
/// This configuration option is considered a workaround for the LIFO
814+
/// slot not being stealable. When the slot becomes stealable, we will
815+
/// revisit whther or not this option is necessary. See
816+
/// tokio-rs/tokio#4941.
817+
///
818+
/// # Examples
819+
///
820+
/// ```
821+
/// use tokio::runtime;
822+
///
823+
/// let rt = runtime::Builder::new_multi_thread()
824+
/// .disable_lifo_slot()
825+
/// .build()
826+
/// .unwrap();
827+
/// ```
828+
pub fn disable_lifo_slot(&mut self) -> &mut Self {
829+
self.disable_lifo_slot = true;
830+
self
831+
}
784832
}
785833

786834
fn build_basic_runtime(&mut self) -> io::Result<Runtime> {
@@ -814,6 +862,7 @@ impl Builder {
814862
event_interval: self.event_interval,
815863
#[cfg(tokio_unstable)]
816864
unhandled_panic: self.unhandled_panic.clone(),
865+
disable_lifo_slot: self.disable_lifo_slot,
817866
},
818867
);
819868
let spawner = Spawner::Basic(scheduler.spawner().clone());
@@ -932,6 +981,7 @@ cfg_rt_multi_thread! {
932981
event_interval: self.event_interval,
933982
#[cfg(tokio_unstable)]
934983
unhandled_panic: self.unhandled_panic.clone(),
984+
disable_lifo_slot: self.disable_lifo_slot,
935985
},
936986
);
937987
let spawner = Spawner::ThreadPool(scheduler.spawner().clone());

tokio/src/runtime/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![cfg_attr(any(not(feature = "full"), tokio_wasm), allow(dead_code))]
12
use crate::runtime::Callback;
23

34
pub(crate) struct Config {
@@ -13,6 +14,15 @@ pub(crate) struct Config {
1314
/// Callback for a worker unparking itself
1415
pub(crate) after_unpark: Option<Callback>,
1516

17+
/// The multi-threaded scheduler includes a per-worker LIFO slot used to
18+
/// store the last scheduled task. This can improve certain usage patterns,
19+
/// especially message passing between tasks. However, this LIFO slot is not
20+
/// currently stealable.
21+
///
22+
/// Eventually, the LIFO slot **will** become stealable, however as a
23+
/// stop-gap, this unstable option lets users disable the LIFO task.
24+
pub(crate) disable_lifo_slot: bool,
25+
1626
#[cfg(tokio_unstable)]
1727
/// How to respond to unhandled task panics.
1828
pub(crate) unhandled_panic: crate::runtime::UnhandledPanic,

tokio/src/runtime/thread_pool/worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ impl Shared {
758758
// task must always be pushed to the back of the queue, enabling other
759759
// tasks to be executed. If **not** a yield, then there is more
760760
// flexibility and the task may go to the front of the queue.
761-
let should_notify = if is_yield {
761+
let should_notify = if is_yield || self.config.disable_lifo_slot {
762762
core.run_queue
763763
.push_back(task, &self.inject, &mut core.metrics);
764764
true

tokio/tests/rt_threaded.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,27 @@ async fn test_block_in_place4() {
542542
fn rt() -> runtime::Runtime {
543543
runtime::Runtime::new().unwrap()
544544
}
545+
546+
#[cfg(tokio_unstable)]
547+
mod unstable {
548+
use super::*;
549+
550+
#[test]
551+
fn test_disable_lifo_slot() {
552+
let rt = runtime::Builder::new_multi_thread()
553+
.disable_lifo_slot()
554+
.worker_threads(2)
555+
.build()
556+
.unwrap();
557+
558+
rt.block_on(async {
559+
tokio::spawn(async {
560+
// Spawn another task and block the thread until completion. If the LIFO slot
561+
// is used then the test doesn't complete.
562+
futures::executor::block_on(tokio::spawn(async {})).unwrap();
563+
})
564+
.await
565+
.unwrap();
566+
})
567+
}
568+
}

0 commit comments

Comments
 (0)