Skip to content

Commit 8832e93

Browse files
authored
rt(alt): fix a number of concurrency bugs (#5907)
Expands loom coverage and fixes a number of bugs. Closes #5888
1 parent dbda204 commit 8832e93

File tree

12 files changed

+330
-112
lines changed

12 files changed

+330
-112
lines changed

tokio/Cargo.toml

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

159159
[target.'cfg(loom)'.dev-dependencies]
160-
loom = { version = "0.6", features = ["futures", "checkpoint"] }
160+
loom = { version = "0.7", features = ["futures", "checkpoint"] }
161161

162162
[package.metadata.docs.rs]
163163
all-features = true

tokio/src/loom/mocked.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub(crate) mod sync {
1515
}
1616

1717
#[inline]
18+
#[track_caller]
1819
pub(crate) fn lock(&self) -> MutexGuard<'_, T> {
1920
self.0.lock().unwrap()
2021
}

tokio/src/runtime/builder.rs

+19
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ pub struct Builder {
9393
/// How many ticks before yielding to the driver for timer and I/O events?
9494
pub(super) event_interval: u32,
9595

96+
pub(super) local_queue_capacity: usize,
97+
9698
/// When true, the multi-threade scheduler LIFO slot should not be used.
9799
///
98100
/// This option should only be exposed as unstable.
@@ -297,6 +299,12 @@ impl Builder {
297299
global_queue_interval: None,
298300
event_interval,
299301

302+
#[cfg(not(loom))]
303+
local_queue_capacity: 256,
304+
305+
#[cfg(loom)]
306+
local_queue_capacity: 4,
307+
300308
seed_generator: RngSeedGenerator::new(RngSeed::new()),
301309

302310
#[cfg(tokio_unstable)]
@@ -1046,6 +1054,14 @@ impl Builder {
10461054
}
10471055
}
10481056

1057+
cfg_loom! {
1058+
pub(crate) fn local_queue_capacity(&mut self, value: usize) -> &mut Self {
1059+
assert!(value.is_power_of_two());
1060+
self.local_queue_capacity = value;
1061+
self
1062+
}
1063+
}
1064+
10491065
fn build_current_thread_runtime(&mut self) -> io::Result<Runtime> {
10501066
use crate::runtime::scheduler::{self, CurrentThread};
10511067
use crate::runtime::{runtime::Scheduler, Config};
@@ -1074,6 +1090,7 @@ impl Builder {
10741090
after_unpark: self.after_unpark.clone(),
10751091
global_queue_interval: self.global_queue_interval,
10761092
event_interval: self.event_interval,
1093+
local_queue_capacity: self.local_queue_capacity,
10771094
#[cfg(tokio_unstable)]
10781095
unhandled_panic: self.unhandled_panic.clone(),
10791096
disable_lifo_slot: self.disable_lifo_slot,
@@ -1224,6 +1241,7 @@ cfg_rt_multi_thread! {
12241241
after_unpark: self.after_unpark.clone(),
12251242
global_queue_interval: self.global_queue_interval,
12261243
event_interval: self.event_interval,
1244+
local_queue_capacity: self.local_queue_capacity,
12271245
#[cfg(tokio_unstable)]
12281246
unhandled_panic: self.unhandled_panic.clone(),
12291247
disable_lifo_slot: self.disable_lifo_slot,
@@ -1271,6 +1289,7 @@ cfg_rt_multi_thread! {
12711289
after_unpark: self.after_unpark.clone(),
12721290
global_queue_interval: self.global_queue_interval,
12731291
event_interval: self.event_interval,
1292+
local_queue_capacity: self.local_queue_capacity,
12741293
#[cfg(tokio_unstable)]
12751294
unhandled_panic: self.unhandled_panic.clone(),
12761295
disable_lifo_slot: self.disable_lifo_slot,

tokio/src/runtime/config.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
#![cfg_attr(any(not(feature = "full"), target_family = "wasm"), allow(dead_code))]
1+
#![cfg_attr(
2+
any(not(all(tokio_unstable, feature = "full")), target_family = "wasm"),
3+
allow(dead_code)
4+
)]
25
use crate::runtime::Callback;
36
use crate::util::RngSeedGenerator;
47

@@ -9,6 +12,9 @@ pub(crate) struct Config {
912
/// How many ticks before yielding to the driver for timer and I/O events?
1013
pub(crate) event_interval: u32,
1114

15+
/// How big to make each worker's local queue
16+
pub(crate) local_queue_capacity: usize,
17+
1218
/// Callback for a worker parking itself
1319
pub(crate) before_park: Option<Callback>,
1420

tokio/src/runtime/driver.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
// Eventually, this file will see significant refactoring / cleanup. For now, we
44
// don't need to worry much about dead code with certain feature permutations.
5-
#![cfg_attr(not(feature = "full"), allow(dead_code))]
5+
#![cfg_attr(
6+
any(not(all(tokio_unstable, feature = "full")), target_family = "wasm"),
7+
allow(dead_code)
8+
)]
69

710
use crate::runtime::park::{ParkThread, UnparkThread};
811

@@ -58,6 +61,10 @@ impl Driver {
5861
))
5962
}
6063

64+
pub(crate) fn is_enabled(&self) -> bool {
65+
self.inner.is_enabled()
66+
}
67+
6168
pub(crate) fn park(&mut self, handle: &Handle) {
6269
self.inner.park(handle)
6370
}
@@ -154,6 +161,13 @@ cfg_io_driver! {
154161
}
155162

156163
impl IoStack {
164+
pub(crate) fn is_enabled(&self) -> bool {
165+
match self {
166+
IoStack::Enabled(..) => true,
167+
IoStack::Disabled(..) => false,
168+
}
169+
}
170+
157171
pub(crate) fn park(&mut self, handle: &Handle) {
158172
match self {
159173
IoStack::Enabled(v) => v.park(handle),
@@ -217,6 +231,11 @@ cfg_not_io_driver! {
217231
pub(crate) fn shutdown(&mut self, _handle: &Handle) {
218232
self.0.shutdown();
219233
}
234+
235+
/// This is not a "real" driver, so it is not considered enabled.
236+
pub(crate) fn is_enabled(&self) -> bool {
237+
false
238+
}
220239
}
221240
}
222241

@@ -298,6 +317,13 @@ cfg_time! {
298317
}
299318

300319
impl TimeDriver {
320+
pub(crate) fn is_enabled(&self) -> bool {
321+
match self {
322+
TimeDriver::Enabled { .. } => true,
323+
TimeDriver::Disabled(inner) => inner.is_enabled(),
324+
}
325+
}
326+
301327
pub(crate) fn park(&mut self, handle: &Handle) {
302328
match self {
303329
TimeDriver::Enabled { driver, .. } => driver.park(handle),

tokio/src/runtime/scheduler/inject/shared.rs

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ impl<T: 'static> Shared<T> {
109109
pub(crate) unsafe fn pop_n<'a>(&'a self, synced: &'a mut Synced, n: usize) -> Pop<'a, T> {
110110
use std::cmp;
111111

112+
debug_assert!(n > 0);
113+
112114
// safety: All updates to the len atomic are guarded by the mutex. As
113115
// such, a non-atomic load followed by a store is safe.
114116
let len = self.len.unsync_load();

tokio/src/runtime/scheduler/inject/synced.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#![cfg_attr(
2+
any(not(all(tokio_unstable, feature = "full")), target_family = "wasm"),
3+
allow(dead_code)
4+
)]
5+
16
use crate::runtime::task;
27

38
pub(crate) struct Synced {
@@ -29,4 +34,8 @@ impl Synced {
2934
// safety: a `Notified` is pushed into the queue and now it is popped!
3035
Some(unsafe { task::Notified::from_raw(task) })
3136
}
37+
38+
pub(crate) fn is_empty(&self) -> bool {
39+
self.head.is_none()
40+
}
3241
}

tokio/src/runtime/scheduler/multi_thread_alt/idle.rs

+13-15
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ impl Idle {
6060
(idle, synced)
6161
}
6262

63+
pub(super) fn needs_searching(&self) -> bool {
64+
self.needs_searching.load(Acquire)
65+
}
66+
6367
pub(super) fn num_idle(&self, synced: &Synced) -> usize {
68+
#[cfg(not(loom))]
6469
debug_assert_eq!(synced.available_cores.len(), self.num_idle.load(Acquire));
6570
synced.available_cores.len()
6671
}
@@ -131,13 +136,7 @@ impl Idle {
131136
}
132137

133138
// We need to establish a stronger barrier than with `notify_local`
134-
if self
135-
.num_searching
136-
.compare_exchange(0, 1, AcqRel, Acquire)
137-
.is_err()
138-
{
139-
return;
140-
}
139+
self.num_searching.fetch_add(1, AcqRel);
141140

142141
self.notify_synced(synced, shared);
143142
}
@@ -158,6 +157,7 @@ impl Idle {
158157
synced.assigned_cores[worker] = Some(core);
159158

160159
let num_idle = synced.idle.available_cores.len();
160+
#[cfg(not(loom))]
161161
debug_assert_eq!(num_idle, self.num_idle.load(Acquire) - 1);
162162

163163
// Update the number of sleeping workers
@@ -221,6 +221,7 @@ impl Idle {
221221
let num_idle = synced.idle.available_cores.len();
222222
self.num_idle.store(num_idle, Release);
223223
} else {
224+
#[cfg(not(loom))]
224225
debug_assert_eq!(
225226
synced.idle.available_cores.len(),
226227
self.num_idle.load(Acquire)
@@ -260,11 +261,11 @@ impl Idle {
260261
// The core should not be searching at this point
261262
debug_assert!(!core.is_searching);
262263

263-
// Check that this isn't the final worker to go idle *and*
264-
// `needs_searching` is set.
265-
debug_assert!(!self.needs_searching.load(Acquire) || num_active_workers(&synced.idle) > 1);
264+
// Check that there are no pending tasks in the global queue
265+
debug_assert!(synced.inject.is_empty());
266266

267267
let num_idle = synced.idle.available_cores.len();
268+
#[cfg(not(loom))]
268269
debug_assert_eq!(num_idle, self.num_idle.load(Acquire));
269270

270271
self.idle_map.set(core.index);
@@ -314,7 +315,7 @@ impl Idle {
314315
}
315316
}
316317

317-
fn transition_worker_to_searching(&self, core: &mut Core) {
318+
pub(super) fn transition_worker_to_searching(&self, core: &mut Core) {
318319
core.is_searching = true;
319320
self.num_searching.fetch_add(1, AcqRel);
320321
self.needs_searching.store(false, Release);
@@ -324,10 +325,7 @@ impl Idle {
324325
///
325326
/// Returns `true` if this is the final searching worker. The caller
326327
/// **must** notify a new worker.
327-
pub(super) fn transition_worker_from_searching(&self, core: &mut Core) -> bool {
328-
debug_assert!(core.is_searching);
329-
core.is_searching = false;
330-
328+
pub(super) fn transition_worker_from_searching(&self) -> bool {
331329
let prev = self.num_searching.fetch_sub(1, AcqRel);
332330
debug_assert!(prev > 0);
333331

0 commit comments

Comments
 (0)