Skip to content

Commit 4165601

Browse files
authored
rt: initial implementation of new threaded runtime (#5823)
This patch includes an initial implementation of a new multi-threaded runtime. The new runtime aims to increase the scheduler throughput by speeding up how it dispatches work to peer worker threads. This implementation improves most benchmarks by about ~10% when the number of threads is below 16. As threads increase, mutex contention deteriorates performance. Because the new scheduler is not yet ready to replace the old one, the patch introduces it as an unstable runtime flavor with a warning that it isn't production ready. Work to improve the scalability of the runtime will most likely require more intrusive changes across Tokio, so I am opting to merge with master to avoid larger conflicts.
1 parent 63577cd commit 4165601

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+5415
-105
lines changed

.github/labeler.yml

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11

2-
R-loom:
2+
R-loom-sync:
33
- tokio/src/sync/*
44
- tokio/src/sync/**/*
5-
- tokio-util/src/sync/*
6-
- tokio-util/src/sync/**/*
7-
- tokio/src/runtime/*
8-
- tokio/src/runtime/**/*
5+
6+
R-loom-time-driver:
7+
- tokio/src/runtime/time/*
8+
- tokio/src/runtime/time/**/*
9+
10+
R-loom-current-thread:
11+
- tokio/src/runtime/scheduler/*
12+
- tokio/src/runtime/scheduler/current_thread/*
13+
- tokio/src/runtime/task/*
14+
- tokio/src/runtime/task/**
15+
16+
R-loom-multi-thread:
17+
- tokio/src/runtime/scheduler/*
18+
- tokio/src/runtime/scheduler/multi_thread/*
19+
- tokio/src/runtime/scheduler/multi_thread/**
20+
- tokio/src/runtime/task/*
21+
- tokio/src/runtime/task/**
22+
23+
R-loom-multi-thread-alt:
24+
- tokio/src/runtime/scheduler/*
25+
- tokio/src/runtime/scheduler/multi_thread_alt/*
26+
- tokio/src/runtime/scheduler/multi_thread_alt/**
27+
- tokio/src/runtime/task/*
28+
- tokio/src/runtime/task/**

.github/workflows/loom.yml

+86-20
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ on:
88
name: Loom
99

1010
env:
11-
RUSTFLAGS: -Dwarnings
11+
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
12+
LOOM_MAX_PREEMPTIONS: 2
13+
LOOM_MAX_BRANCHES: 10000
1214
RUST_BACKTRACE: 1
1315
# Change to specific Rust release to pin
1416
rust_stable: stable
@@ -17,26 +19,91 @@ permissions:
1719
contents: read
1820

1921
jobs:
20-
loom:
21-
name: loom
22+
loom-sync:
23+
name: loom tokio::sync
2224
# base_ref is null when it's not a pull request
23-
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null))
25+
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v3
29+
- name: Install Rust ${{ env.rust_stable }}
30+
uses: dtolnay/rust-toolchain@master
31+
with:
32+
toolchain: ${{ env.rust_stable }}
33+
- uses: Swatinem/rust-cache@v2
34+
- name: run tests
35+
run: cargo test --lib --release --features full -- --nocapture sync::tests
36+
working-directory: tokio
37+
38+
loom-time-driver:
39+
name: loom time driver
40+
# base_ref is null when it's not a pull request
41+
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v3
45+
- name: Install Rust ${{ env.rust_stable }}
46+
uses: dtolnay/rust-toolchain@master
47+
with:
48+
toolchain: ${{ env.rust_stable }}
49+
- uses: Swatinem/rust-cache@v2
50+
- name: run tests
51+
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
52+
working-directory: tokio
53+
54+
loom-current-thread:
55+
name: loom current-thread scheduler
56+
# base_ref is null when it's not a pull request
57+
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v3
61+
- name: Install Rust ${{ env.rust_stable }}
62+
uses: dtolnay/rust-toolchain@master
63+
with:
64+
toolchain: ${{ env.rust_stable }}
65+
- uses: Swatinem/rust-cache@v2
66+
- name: run tests
67+
run: cargo test --lib --release --features full -- --nocapture loom_current_thread
68+
working-directory: tokio
69+
70+
loom-multi-thread:
71+
name: loom multi-thread scheduler
72+
# base_ref is null when it's not a pull request
73+
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread') || (github.base_ref == null))
74+
runs-on: ubuntu-latest
75+
strategy:
76+
matrix:
77+
include:
78+
- scope: loom_multi_thread::group_a
79+
- scope: loom_multi_thread::group_b
80+
- scope: loom_multi_thread::group_c
81+
- scope: loom_multi_thread::group_d
82+
steps:
83+
- uses: actions/checkout@v3
84+
- name: Install Rust ${{ env.rust_stable }}
85+
uses: dtolnay/rust-toolchain@master
86+
with:
87+
toolchain: ${{ env.rust_stable }}
88+
- uses: Swatinem/rust-cache@v2
89+
- name: loom ${{ matrix.scope }}
90+
run: cargo test --lib --release --features full -- $SCOPE
91+
working-directory: tokio
92+
env:
93+
SCOPE: ${{ matrix.scope }}
94+
95+
loom-multi-thread-alt:
96+
name: loom ALT multi-thread scheduler
97+
# base_ref is null when it's not a pull request
98+
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
2499
runs-on: ubuntu-latest
25100
strategy:
26101
matrix:
27102
include:
28-
- scope: --skip loom_pool
29-
max_preemptions: 2
30-
- scope: loom_pool::group_a
31-
max_preemptions: 2
32-
- scope: loom_pool::group_b
33-
max_preemptions: 2
34-
- scope: loom_pool::group_c
35-
max_preemptions: 2
36-
- scope: loom_pool::group_d
37-
max_preemptions: 2
38-
- scope: time::driver
39-
max_preemptions: 2
103+
- scope: loom_multi_thread_alt::group_a
104+
- scope: loom_multi_thread_alt::group_b
105+
- scope: loom_multi_thread_alt::group_c
106+
- scope: loom_multi_thread_alt::group_d
40107
steps:
41108
- uses: actions/checkout@v3
42109
- name: Install Rust ${{ env.rust_stable }}
@@ -45,10 +112,9 @@ jobs:
45112
toolchain: ${{ env.rust_stable }}
46113
- uses: Swatinem/rust-cache@v2
47114
- name: loom ${{ matrix.scope }}
48-
run: cargo test --lib --release --features full -- --nocapture $SCOPE
115+
run: cargo test --lib --release --features full -- $SCOPE
49116
working-directory: tokio
50117
env:
51-
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings -C debug-assertions
52-
LOOM_MAX_PREEMPTIONS: ${{ matrix.max_preemptions }}
53-
LOOM_MAX_BRANCHES: 10000
54118
SCOPE: ${{ matrix.scope }}
119+
# TODO: remove this before stabilizing
120+
LOOM_MAX_PREEMPTIONS: 1

tokio/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ wasm-bindgen-test = "0.3.0"
160160
mio-aio = { version = "0.7.0", features = ["tokio"] }
161161

162162
[target.'cfg(loom)'.dev-dependencies]
163-
loom = { version = "0.5.2", features = ["futures", "checkpoint"] }
163+
loom = { version = "0.6", features = ["futures", "checkpoint"] }
164164

165165
[package.metadata.docs.rs]
166166
all-features = true

tokio/src/loom/std/unsafe_cell.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ impl<T> UnsafeCell<T> {
66
UnsafeCell(std::cell::UnsafeCell::new(data))
77
}
88

9+
#[inline(always)]
910
pub(crate) fn with<R>(&self, f: impl FnOnce(*const T) -> R) -> R {
1011
f(self.0.get())
1112
}
1213

14+
#[inline(always)]
1315
pub(crate) fn with_mut<R>(&self, f: impl FnOnce(*mut T) -> R) -> R {
1416
f(self.0.get())
1517
}

tokio/src/runtime/blocking/schedule.rs

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ impl BlockingSchedule {
2525
}
2626
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
2727
scheduler::Handle::MultiThread(_) => {}
28+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
29+
scheduler::Handle::MultiThreadAlt(_) => {}
2830
}
2931
}
3032
BlockingSchedule {
@@ -45,6 +47,8 @@ impl task::Schedule for BlockingSchedule {
4547
}
4648
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
4749
scheduler::Handle::MultiThread(_) => {}
50+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
51+
scheduler::Handle::MultiThreadAlt(_) => {}
4852
}
4953
}
5054
None

tokio/src/runtime/builder.rs

+68
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ pub(crate) enum Kind {
199199
CurrentThread,
200200
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
201201
MultiThread,
202+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
203+
MultiThreadAlt,
202204
}
203205

204206
impl Builder {
@@ -230,6 +232,26 @@ impl Builder {
230232
// The number `61` is fairly arbitrary. I believe this value was copied from golang.
231233
Builder::new(Kind::MultiThread, 61)
232234
}
235+
236+
cfg_unstable! {
237+
/// Returns a new builder with the alternate multi thread scheduler
238+
/// selected.
239+
///
240+
/// The alternate multi threaded scheduler is an in-progress
241+
/// candidate to replace the existing multi threaded scheduler. It
242+
/// currently does not scale as well to 16+ processors.
243+
///
244+
/// This runtime flavor is currently **not considered production
245+
/// ready**.
246+
///
247+
/// Configuration methods can be chained on the return value.
248+
#[cfg(feature = "rt-multi-thread")]
249+
#[cfg_attr(docsrs, doc(cfg(feature = "rt-multi-thread")))]
250+
pub fn new_multi_thread_alt() -> Builder {
251+
// The number `61` is fairly arbitrary. I believe this value was copied from golang.
252+
Builder::new(Kind::MultiThreadAlt, 61)
253+
}
254+
}
233255
}
234256

235257
/// Returns a new runtime builder initialized with default configuration
@@ -656,6 +678,8 @@ impl Builder {
656678
Kind::CurrentThread => self.build_current_thread_runtime(),
657679
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
658680
Kind::MultiThread => self.build_threaded_runtime(),
681+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
682+
Kind::MultiThreadAlt => self.build_alt_threaded_runtime(),
659683
}
660684
}
661685

@@ -665,6 +689,8 @@ impl Builder {
665689
Kind::CurrentThread => true,
666690
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
667691
Kind::MultiThread => false,
692+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
693+
Kind::MultiThreadAlt => false,
668694
},
669695
enable_io: self.enable_io,
670696
enable_time: self.enable_time,
@@ -1214,6 +1240,48 @@ cfg_rt_multi_thread! {
12141240

12151241
Ok(Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool))
12161242
}
1243+
1244+
cfg_unstable! {
1245+
fn build_alt_threaded_runtime(&mut self) -> io::Result<Runtime> {
1246+
use crate::loom::sys::num_cpus;
1247+
use crate::runtime::{Config, runtime::Scheduler};
1248+
use crate::runtime::scheduler::MultiThreadAlt;
1249+
1250+
let core_threads = self.worker_threads.unwrap_or_else(num_cpus);
1251+
1252+
let (driver, driver_handle) = driver::Driver::new(self.get_cfg())?;
1253+
1254+
// Create the blocking pool
1255+
let blocking_pool =
1256+
blocking::create_blocking_pool(self, self.max_blocking_threads + core_threads);
1257+
let blocking_spawner = blocking_pool.spawner().clone();
1258+
1259+
// Generate a rng seed for this runtime.
1260+
let seed_generator_1 = self.seed_generator.next_generator();
1261+
let seed_generator_2 = self.seed_generator.next_generator();
1262+
1263+
let (scheduler, handle) = MultiThreadAlt::new(
1264+
core_threads,
1265+
driver,
1266+
driver_handle,
1267+
blocking_spawner,
1268+
seed_generator_2,
1269+
Config {
1270+
before_park: self.before_park.clone(),
1271+
after_unpark: self.after_unpark.clone(),
1272+
global_queue_interval: self.global_queue_interval,
1273+
event_interval: self.event_interval,
1274+
#[cfg(tokio_unstable)]
1275+
unhandled_panic: self.unhandled_panic.clone(),
1276+
disable_lifo_slot: self.disable_lifo_slot,
1277+
seed_generator: seed_generator_1,
1278+
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
1279+
},
1280+
);
1281+
1282+
Ok(Runtime::from_parts(Scheduler::MultiThreadAlt(scheduler), handle, blocking_pool))
1283+
}
1284+
}
12171285
}
12181286
}
12191287

tokio/src/runtime/handle.rs

+6
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ impl Handle {
357357
scheduler::Handle::CurrentThread(_) => RuntimeFlavor::CurrentThread,
358358
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
359359
scheduler::Handle::MultiThread(_) => RuntimeFlavor::MultiThread,
360+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
361+
scheduler::Handle::MultiThreadAlt(_) => RuntimeFlavor::MultiThreadAlt,
360362
}
361363
}
362364

@@ -385,6 +387,8 @@ impl Handle {
385387
scheduler::Handle::CurrentThread(handle) => handle.owned_id(),
386388
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
387389
scheduler::Handle::MultiThread(handle) => handle.owned_id(),
390+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
391+
scheduler::Handle::MultiThreadAlt(handle) => handle.owned_id(),
388392
};
389393
owned_id.into()
390394
}
@@ -535,6 +539,8 @@ cfg_taskdump! {
535539
handle.dump().await
536540
}).await
537541
},
542+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
543+
scheduler::Handle::MultiThreadAlt(_) => panic!("task dump not implemented for this runtime flavor"),
538544
}
539545
}
540546
}

tokio/src/runtime/runtime.rs

+19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ use std::time::Duration;
99
cfg_rt_multi_thread! {
1010
use crate::runtime::Builder;
1111
use crate::runtime::scheduler::MultiThread;
12+
13+
cfg_unstable! {
14+
use crate::runtime::scheduler::MultiThreadAlt;
15+
}
1216
}
1317

1418
/// The Tokio runtime.
@@ -109,6 +113,9 @@ pub enum RuntimeFlavor {
109113
CurrentThread,
110114
/// The flavor that executes tasks across multiple threads.
111115
MultiThread,
116+
/// The flavor that executes tasks across multiple threads.
117+
#[cfg(tokio_unstable)]
118+
MultiThreadAlt,
112119
}
113120

114121
/// The runtime scheduler is either a multi-thread or a current-thread executor.
@@ -120,6 +127,10 @@ pub(super) enum Scheduler {
120127
/// Execute tasks across multiple threads.
121128
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
122129
MultiThread(MultiThread),
130+
131+
/// Execute tasks across multiple threads.
132+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
133+
MultiThreadAlt(MultiThreadAlt),
123134
}
124135

125136
impl Runtime {
@@ -336,6 +347,8 @@ impl Runtime {
336347
Scheduler::CurrentThread(exec) => exec.block_on(&self.handle.inner, future),
337348
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
338349
Scheduler::MultiThread(exec) => exec.block_on(&self.handle.inner, future),
350+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
351+
Scheduler::MultiThreadAlt(exec) => exec.block_on(&self.handle.inner, future),
339352
}
340353
}
341354

@@ -456,6 +469,12 @@ impl Drop for Runtime {
456469
// already in the runtime's context.
457470
multi_thread.shutdown(&self.handle.inner);
458471
}
472+
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(tokio_wasi)))]
473+
Scheduler::MultiThreadAlt(multi_thread) => {
474+
// The threaded scheduler drops its tasks on its worker threads, which is
475+
// already in the runtime's context.
476+
multi_thread.shutdown(&self.handle.inner);
477+
}
459478
}
460479
}
461480
}

0 commit comments

Comments
 (0)