mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-06 00:00:10 +02:00
time: add alternative timer for better multicore scalability (#7467)
This change introduces per-worker timer wheels in the time subsystem to reduce the lock contention. Key changes: - Each worker now maintains a local timer wheel. - Timer insertions are performed locally. - Timer cancellations are forwarded via a dedicated cross-worker cancellation queue. Relevant RFC: https://github.com/tokio-rs/tokio/issues/7384 --------- Signed-off-by: ADD-SP <[email protected]>
This commit is contained in:
@@ -1058,6 +1058,7 @@ jobs:
|
|||||||
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
||||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -S threads=y --"
|
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -S threads=y --"
|
||||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||||
|
RUSTDOCFLAGS: -C link-args=--max-memory=67108864
|
||||||
|
|
||||||
- name: WASI test tokio-stream
|
- name: WASI test tokio-stream
|
||||||
run: cargo test -p tokio-stream --target ${{ matrix.target }} --features time,net,io-util,sync
|
run: cargo test -p tokio-stream --target ${{ matrix.target }} --features time,net,io-util,sync
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
toolchain: ${{ env.rust_stable }}
|
toolchain: ${{ env.rust_stable }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
run: cargo test --lib --release --features full -- --nocapture runtime::time
|
||||||
working-directory: tokio
|
working-directory: tokio
|
||||||
|
|
||||||
loom-current-thread:
|
loom-current-thread:
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
308
|
311
|
||||||
&
|
&
|
||||||
+
|
+
|
||||||
<
|
<
|
||||||
@@ -64,6 +64,7 @@ codec
|
|||||||
codecs
|
codecs
|
||||||
combinator
|
combinator
|
||||||
combinators
|
combinators
|
||||||
|
condvar
|
||||||
config
|
config
|
||||||
Config
|
Config
|
||||||
connectionless
|
connectionless
|
||||||
@@ -163,6 +164,7 @@ Lauck
|
|||||||
libc
|
libc
|
||||||
lifecycle
|
lifecycle
|
||||||
lifo
|
lifo
|
||||||
|
LLVM
|
||||||
lookups
|
lookups
|
||||||
macOS
|
macOS
|
||||||
MacOS
|
MacOS
|
||||||
@@ -307,3 +309,4 @@ Wakers
|
|||||||
wakeup
|
wakeup
|
||||||
wakeups
|
wakeups
|
||||||
workstealing
|
workstealing
|
||||||
|
ZST
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
|||||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||||
tokio-util = { version = "0.7", path = "../tokio-util", features = ["rt"] }
|
tokio-util = { version = "0.7", path = "../tokio-util", features = ["rt"] }
|
||||||
futures = { version = "0.3.0", features = ["async-await"] }
|
futures = { version = "0.3.0", features = ["async-await"] }
|
||||||
|
futures-test = "0.3.31"
|
||||||
mockall = "0.13.0"
|
mockall = "0.13.0"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
futures-concurrency = "7.6.3"
|
futures-concurrency = "7.6.3"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#![cfg_attr(loom, allow(unused_imports))]
|
#![cfg_attr(loom, allow(unused_imports))]
|
||||||
|
|
||||||
use crate::runtime::handle::Handle;
|
use crate::runtime::handle::Handle;
|
||||||
use crate::runtime::{blocking, driver, Callback, HistogramBuilder, Runtime, TaskCallback};
|
use crate::runtime::{
|
||||||
|
blocking, driver, Callback, HistogramBuilder, Runtime, TaskCallback, TimerFlavor,
|
||||||
|
};
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
use crate::runtime::{metrics::HistogramConfiguration, LocalOptions, LocalRuntime, TaskMeta};
|
use crate::runtime::{metrics::HistogramConfiguration, LocalOptions, LocalRuntime, TaskMeta};
|
||||||
use crate::util::rand::{RngSeed, RngSeedGenerator};
|
use crate::util::rand::{RngSeed, RngSeedGenerator};
|
||||||
@@ -133,6 +135,8 @@ pub struct Builder {
|
|||||||
|
|
||||||
#[cfg(tokio_unstable)]
|
#[cfg(tokio_unstable)]
|
||||||
pub(super) unhandled_panic: UnhandledPanic,
|
pub(super) unhandled_panic: UnhandledPanic,
|
||||||
|
|
||||||
|
timer_flavor: TimerFlavor,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_unstable! {
|
cfg_unstable! {
|
||||||
@@ -318,6 +322,8 @@ impl Builder {
|
|||||||
metrics_poll_count_histogram: HistogramBuilder::default(),
|
metrics_poll_count_histogram: HistogramBuilder::default(),
|
||||||
|
|
||||||
disable_lifo_slot: false,
|
disable_lifo_slot: false,
|
||||||
|
|
||||||
|
timer_flavor: TimerFlavor::Traditional,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,6 +369,41 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables the alternative timer implementation, which is disabled by default.
|
||||||
|
///
|
||||||
|
/// The alternative timer implementation is an unstable feature that may
|
||||||
|
/// provide better performance on multi-threaded runtimes with a large number
|
||||||
|
/// of worker threads.
|
||||||
|
///
|
||||||
|
/// This option only applies to multi-threaded runtimes. Attempting to use
|
||||||
|
/// this option with any other runtime type will have no effect.
|
||||||
|
///
|
||||||
|
/// [Click here to share your experience with the alternative timer](https://github.com/tokio-rs/tokio/issues/7745)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[cfg(not(target_family = "wasm"))]
|
||||||
|
/// # {
|
||||||
|
/// use tokio::runtime;
|
||||||
|
///
|
||||||
|
/// let rt = runtime::Builder::new_multi_thread()
|
||||||
|
/// .enable_alt_timer()
|
||||||
|
/// .build()
|
||||||
|
/// .unwrap();
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread")))
|
||||||
|
)]
|
||||||
|
pub fn enable_alt_timer(&mut self) -> &mut Self {
|
||||||
|
self.enable_time();
|
||||||
|
self.timer_flavor = TimerFlavor::Alternative;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the number of worker threads the `Runtime` will use.
|
/// Sets the number of worker threads the `Runtime` will use.
|
||||||
///
|
///
|
||||||
/// This can be any number above 0 though it is advised to keep this value
|
/// This can be any number above 0 though it is advised to keep this value
|
||||||
@@ -992,6 +1033,7 @@ impl Builder {
|
|||||||
enable_time: self.enable_time,
|
enable_time: self.enable_time,
|
||||||
start_paused: self.start_paused,
|
start_paused: self.start_paused,
|
||||||
nevents: self.nevents,
|
nevents: self.nevents,
|
||||||
|
timer_flavor: self.timer_flavor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1544,7 +1586,9 @@ impl Builder {
|
|||||||
use crate::runtime::scheduler;
|
use crate::runtime::scheduler;
|
||||||
use crate::runtime::Config;
|
use crate::runtime::Config;
|
||||||
|
|
||||||
let (driver, driver_handle) = driver::Driver::new(self.get_cfg())?;
|
let mut cfg = self.get_cfg();
|
||||||
|
cfg.timer_flavor = TimerFlavor::Traditional;
|
||||||
|
let (driver, driver_handle) = driver::Driver::new(cfg)?;
|
||||||
|
|
||||||
// Blocking pool
|
// Blocking pool
|
||||||
let blocking_pool = blocking::create_blocking_pool(self, self.max_blocking_threads);
|
let blocking_pool = blocking::create_blocking_pool(self, self.max_blocking_threads);
|
||||||
@@ -1761,6 +1805,7 @@ cfg_rt_multi_thread! {
|
|||||||
seed_generator: seed_generator_1,
|
seed_generator: seed_generator_1,
|
||||||
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
||||||
},
|
},
|
||||||
|
self.timer_flavor,
|
||||||
);
|
);
|
||||||
|
|
||||||
let handle = Handle { inner: scheduler::Handle::MultiThread(handle) };
|
let handle = Handle { inner: scheduler::Handle::MultiThread(handle) };
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub(crate) struct Cfg {
|
|||||||
pub(crate) enable_pause_time: bool,
|
pub(crate) enable_pause_time: bool,
|
||||||
pub(crate) start_paused: bool,
|
pub(crate) start_paused: bool,
|
||||||
pub(crate) nevents: usize,
|
pub(crate) nevents: usize,
|
||||||
|
pub(crate) timer_flavor: crate::runtime::TimerFlavor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Driver {
|
impl Driver {
|
||||||
@@ -48,7 +49,8 @@ impl Driver {
|
|||||||
|
|
||||||
let clock = create_clock(cfg.enable_pause_time, cfg.start_paused);
|
let clock = create_clock(cfg.enable_pause_time, cfg.start_paused);
|
||||||
|
|
||||||
let (time_driver, time_handle) = create_time_driver(cfg.enable_time, io_stack, &clock);
|
let (time_driver, time_handle) =
|
||||||
|
create_time_driver(cfg.enable_time, cfg.timer_flavor, io_stack, &clock);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Self { inner: time_driver },
|
Self { inner: time_driver },
|
||||||
@@ -113,6 +115,14 @@ impl Handle {
|
|||||||
.expect("A Tokio 1.x context was found, but timers are disabled. Call `enable_time` on the runtime builder to enable timers.")
|
.expect("A Tokio 1.x context was found, but timers are disabled. Call `enable_time` on the runtime builder to enable timers.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
pub(crate) fn with_time<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<&crate::runtime::time::Handle>) -> R,
|
||||||
|
{
|
||||||
|
f(self.time.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn clock(&self) -> &Clock {
|
pub(crate) fn clock(&self) -> &Clock {
|
||||||
&self.clock
|
&self.clock
|
||||||
}
|
}
|
||||||
@@ -281,6 +291,7 @@ cfg_time! {
|
|||||||
Enabled {
|
Enabled {
|
||||||
driver: crate::runtime::time::Driver,
|
driver: crate::runtime::time::Driver,
|
||||||
},
|
},
|
||||||
|
EnabledAlt(IoStack),
|
||||||
Disabled(IoStack),
|
Disabled(IoStack),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,13 +304,21 @@ cfg_time! {
|
|||||||
|
|
||||||
fn create_time_driver(
|
fn create_time_driver(
|
||||||
enable: bool,
|
enable: bool,
|
||||||
|
timer_flavor: crate::runtime::TimerFlavor,
|
||||||
io_stack: IoStack,
|
io_stack: IoStack,
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
) -> (TimeDriver, TimeHandle) {
|
) -> (TimeDriver, TimeHandle) {
|
||||||
if enable {
|
if enable {
|
||||||
let (driver, handle) = crate::runtime::time::Driver::new(io_stack, clock);
|
match timer_flavor {
|
||||||
|
crate::runtime::TimerFlavor::Traditional => {
|
||||||
(TimeDriver::Enabled { driver }, Some(handle))
|
let (driver, handle) = crate::runtime::time::Driver::new(io_stack, clock);
|
||||||
|
(TimeDriver::Enabled { driver }, Some(handle))
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
crate::runtime::TimerFlavor::Alternative => {
|
||||||
|
(TimeDriver::EnabledAlt(io_stack), Some(crate::runtime::time::Driver::new_alt(clock)))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(TimeDriver::Disabled(io_stack), None)
|
(TimeDriver::Disabled(io_stack), None)
|
||||||
}
|
}
|
||||||
@@ -309,6 +328,7 @@ cfg_time! {
|
|||||||
pub(crate) fn park(&mut self, handle: &Handle) {
|
pub(crate) fn park(&mut self, handle: &Handle) {
|
||||||
match self {
|
match self {
|
||||||
TimeDriver::Enabled { driver, .. } => driver.park(handle),
|
TimeDriver::Enabled { driver, .. } => driver.park(handle),
|
||||||
|
TimeDriver::EnabledAlt(v) => v.park(handle),
|
||||||
TimeDriver::Disabled(v) => v.park(handle),
|
TimeDriver::Disabled(v) => v.park(handle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,6 +336,7 @@ cfg_time! {
|
|||||||
pub(crate) fn park_timeout(&mut self, handle: &Handle, duration: Duration) {
|
pub(crate) fn park_timeout(&mut self, handle: &Handle, duration: Duration) {
|
||||||
match self {
|
match self {
|
||||||
TimeDriver::Enabled { driver } => driver.park_timeout(handle, duration),
|
TimeDriver::Enabled { driver } => driver.park_timeout(handle, duration),
|
||||||
|
TimeDriver::EnabledAlt(v) => v.park_timeout(handle, duration),
|
||||||
TimeDriver::Disabled(v) => v.park_timeout(handle, duration),
|
TimeDriver::Disabled(v) => v.park_timeout(handle, duration),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,6 +344,7 @@ cfg_time! {
|
|||||||
pub(crate) fn shutdown(&mut self, handle: &Handle) {
|
pub(crate) fn shutdown(&mut self, handle: &Handle) {
|
||||||
match self {
|
match self {
|
||||||
TimeDriver::Enabled { driver } => driver.shutdown(handle),
|
TimeDriver::Enabled { driver } => driver.shutdown(handle),
|
||||||
|
TimeDriver::EnabledAlt(v) => v.shutdown(handle),
|
||||||
TimeDriver::Disabled(v) => v.shutdown(handle),
|
TimeDriver::Disabled(v) => v.shutdown(handle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,6 +363,7 @@ cfg_not_time! {
|
|||||||
|
|
||||||
fn create_time_driver(
|
fn create_time_driver(
|
||||||
_enable: bool,
|
_enable: bool,
|
||||||
|
_timer_flavor: crate::runtime::TimerFlavor,
|
||||||
io_stack: IoStack,
|
io_stack: IoStack,
|
||||||
_clock: &Clock,
|
_clock: &Clock,
|
||||||
) -> (TimeDriver, TimeHandle) {
|
) -> (TimeDriver, TimeHandle) {
|
||||||
|
|||||||
@@ -389,8 +389,135 @@ cfg_process_driver! {
|
|||||||
mod process;
|
mod process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "time"), allow(dead_code))]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub(crate) enum TimerFlavor {
|
||||||
|
Traditional,
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Alternative,
|
||||||
|
}
|
||||||
|
|
||||||
cfg_time! {
|
cfg_time! {
|
||||||
pub(crate) mod time;
|
pub(crate) mod time;
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) mod time_alt;
|
||||||
|
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Timer {
|
||||||
|
Traditional(time::TimerEntry),
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Alternative(time_alt::Timer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn new(
|
||||||
|
handle: crate::runtime::scheduler::Handle,
|
||||||
|
deadline: crate::time::Instant,
|
||||||
|
) -> Self {
|
||||||
|
match handle.timer_flavor() {
|
||||||
|
crate::runtime::TimerFlavor::Traditional => {
|
||||||
|
Timer::Traditional(time::TimerEntry::new(handle, deadline))
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
crate::runtime::TimerFlavor::Alternative => {
|
||||||
|
Timer::Alternative(time_alt::Timer::new(handle, deadline))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deadline(&self) -> crate::time::Instant {
|
||||||
|
match self {
|
||||||
|
Timer::Traditional(entry) => entry.deadline(),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => entry.deadline(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_elapsed(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Timer::Traditional(entry) => entry.is_elapsed(),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => entry.is_elapsed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn flavor(self: Pin<&Self>) -> TimerFlavor {
|
||||||
|
match self.get_ref() {
|
||||||
|
Timer::Traditional(_) => TimerFlavor::Traditional,
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(_) => TimerFlavor::Alternative,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reset(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
new_time: crate::time::Instant,
|
||||||
|
reregister: bool
|
||||||
|
) {
|
||||||
|
// Safety: we never move the inner entries.
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
match this {
|
||||||
|
Timer::Traditional(entry) => {
|
||||||
|
// Safety: we never move the inner entries.
|
||||||
|
unsafe { Pin::new_unchecked(entry).reset(new_time, reregister); }
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(_) => panic!("not implemented yet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_elapsed(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), crate::time::error::Error>> {
|
||||||
|
// Safety: we never move the inner entries.
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
match this {
|
||||||
|
Timer::Traditional(entry) => {
|
||||||
|
// Safety: we never move the inner entries.
|
||||||
|
unsafe { Pin::new_unchecked(entry).poll_elapsed(cx) }
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => {
|
||||||
|
// Safety: we never move the inner entries.
|
||||||
|
unsafe { Pin::new_unchecked(entry).poll_elapsed(cx).map(Ok) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) fn scheduler_handle(&self) -> &crate::runtime::scheduler::Handle {
|
||||||
|
match self {
|
||||||
|
Timer::Traditional(_) => unreachable!("we should not call this on Traditional Timer"),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => entry.scheduler_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
|
pub(crate) fn driver(self: Pin<&Self>) -> &crate::runtime::time::Handle {
|
||||||
|
match self.get_ref() {
|
||||||
|
Timer::Traditional(entry) => entry.driver(),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => entry.driver(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
|
pub(crate) fn clock(self: Pin<&Self>) -> &crate::time::Clock {
|
||||||
|
match self.get_ref() {
|
||||||
|
Timer::Traditional(entry) => entry.clock(),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Timer::Alternative(entry) => entry.clock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_signal_internal_and_unix! {
|
cfg_signal_internal_and_unix! {
|
||||||
|
|||||||
@@ -387,12 +387,7 @@ impl Context {
|
|||||||
core.metrics.about_to_park();
|
core.metrics.about_to_park();
|
||||||
core.submit_metrics(handle);
|
core.submit_metrics(handle);
|
||||||
|
|
||||||
let (c, ()) = self.enter(core, || {
|
core = self.park_internal(core, handle, &mut driver, None);
|
||||||
driver.park(&handle.driver);
|
|
||||||
self.defer.wake();
|
|
||||||
});
|
|
||||||
|
|
||||||
core = c;
|
|
||||||
|
|
||||||
core.metrics.unparked();
|
core.metrics.unparked();
|
||||||
core.submit_metrics(handle);
|
core.submit_metrics(handle);
|
||||||
@@ -413,12 +408,27 @@ impl Context {
|
|||||||
|
|
||||||
core.submit_metrics(handle);
|
core.submit_metrics(handle);
|
||||||
|
|
||||||
let (mut core, ()) = self.enter(core, || {
|
core = self.park_internal(core, handle, &mut driver, Some(Duration::from_millis(0)));
|
||||||
driver.park_timeout(&handle.driver, Duration::from_millis(0));
|
|
||||||
|
core.driver = Some(driver);
|
||||||
|
core
|
||||||
|
}
|
||||||
|
|
||||||
|
fn park_internal(
|
||||||
|
&self,
|
||||||
|
core: Box<Core>,
|
||||||
|
handle: &Handle,
|
||||||
|
driver: &mut Driver,
|
||||||
|
duration: Option<Duration>,
|
||||||
|
) -> Box<Core> {
|
||||||
|
let (core, ()) = self.enter(core, || {
|
||||||
|
match duration {
|
||||||
|
Some(dur) => driver.park_timeout(&handle.driver, dur),
|
||||||
|
None => driver.park(&handle.driver),
|
||||||
|
}
|
||||||
self.defer.wake();
|
self.defer.wake();
|
||||||
});
|
});
|
||||||
|
|
||||||
core.driver = Some(driver);
|
|
||||||
core
|
core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ cfg_rt_multi_thread! {
|
|||||||
pub(crate) use multi_thread::MultiThread;
|
pub(crate) use multi_thread::MultiThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) mod util;
|
||||||
|
|
||||||
use crate::runtime::driver;
|
use crate::runtime::driver;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -107,6 +109,48 @@ cfg_rt! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
pub(crate) fn timer_flavor(&self) -> crate::runtime::TimerFlavor {
|
||||||
|
match self {
|
||||||
|
Handle::CurrentThread(_) => crate::runtime::TimerFlavor::Traditional,
|
||||||
|
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
Handle::MultiThread(h) => h.timer_flavor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", feature = "time"))]
|
||||||
|
/// Returns true if both handles belong to the same runtime instance.
|
||||||
|
pub(crate) fn is_same_runtime(&self, other: &Handle) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Handle::CurrentThread(a), Handle::CurrentThread(b)) => Arc::ptr_eq(a, b),
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
(Handle::MultiThread(a), Handle::MultiThread(b)) => Arc::ptr_eq(a, b),
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
_ => false, // different runtime types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", feature = "time"))]
|
||||||
|
/// Returns true if the runtime is shutting down.
|
||||||
|
pub(crate) fn is_shutdown(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Handle::CurrentThread(_) => panic!("the alternative timer implementation is not supported on CurrentThread runtime"),
|
||||||
|
Handle::MultiThread(h) => h.is_shutdown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", feature = "time"))]
|
||||||
|
/// Push a timer entry that was created outside of this runtime
|
||||||
|
/// into the runtime-global queue. The pushed timer will be
|
||||||
|
/// processed by a random worker thread.
|
||||||
|
pub(crate) fn push_remote_timer(&self, entry_hdl: crate::runtime::time_alt::EntryHandle) {
|
||||||
|
match self {
|
||||||
|
Handle::CurrentThread(_) => panic!("the alternative timer implementation is not supported on CurrentThread runtime"),
|
||||||
|
Handle::MultiThread(h) => h.push_remote_timer(entry_hdl),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this is a local runtime and the runtime is owned by the current thread.
|
/// Returns true if this is a local runtime and the runtime is owned by the current thread.
|
||||||
pub(crate) fn can_spawn_local_on_local_runtime(&self) -> bool {
|
pub(crate) fn can_spawn_local_on_local_runtime(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -249,6 +293,17 @@ cfg_rt! {
|
|||||||
match_flavor!(self, Context(context) => context.defer(waker));
|
match_flavor!(self, Context(context) => context.defer(waker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) fn with_time_temp_local_context<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<crate::runtime::time_alt::TempLocalContext<'_>>) -> R,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Context::CurrentThread(_) => panic!("the alternative timer implementation is not supported on CurrentThread runtime"),
|
||||||
|
Context::MultiThread(context) => context.with_time_temp_local_context(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg_rt_multi_thread! {
|
cfg_rt_multi_thread! {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn expect_multi_thread(&self) -> &multi_thread::Context {
|
pub(crate) fn expect_multi_thread(&self) -> &multi_thread::Context {
|
||||||
@@ -273,5 +328,11 @@ cfg_not_rt! {
|
|||||||
pub(crate) fn current() -> Handle {
|
pub(crate) fn current() -> Handle {
|
||||||
panic!("{}", crate::util::error::CONTEXT_MISSING_ERROR)
|
panic!("{}", crate::util::error::CONTEXT_MISSING_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "time"), allow(dead_code))]
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn timer_flavor(&self) -> crate::runtime::TimerFlavor {
|
||||||
|
panic!("{}", crate::util::error::CONTEXT_MISSING_ERROR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::runtime::task::{Notified, Task, TaskHarnessScheduleHooks};
|
|||||||
use crate::runtime::{
|
use crate::runtime::{
|
||||||
blocking, driver,
|
blocking, driver,
|
||||||
task::{self, JoinHandle, SpawnLocation},
|
task::{self, JoinHandle, SpawnLocation},
|
||||||
TaskHooks, TaskMeta,
|
TaskHooks, TaskMeta, TimerFlavor,
|
||||||
};
|
};
|
||||||
use crate::util::RngSeedGenerator;
|
use crate::util::RngSeedGenerator;
|
||||||
|
|
||||||
@@ -17,6 +17,9 @@ cfg_taskdump! {
|
|||||||
mod taskdump;
|
mod taskdump;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
use crate::loom::sync::atomic::{AtomicBool, Ordering::SeqCst};
|
||||||
|
|
||||||
/// Handle to the multi thread scheduler
|
/// Handle to the multi thread scheduler
|
||||||
pub(crate) struct Handle {
|
pub(crate) struct Handle {
|
||||||
/// Task spawner
|
/// Task spawner
|
||||||
@@ -33,6 +36,14 @@ pub(crate) struct Handle {
|
|||||||
|
|
||||||
/// User-supplied hooks to invoke for things
|
/// User-supplied hooks to invoke for things
|
||||||
pub(crate) task_hooks: TaskHooks,
|
pub(crate) task_hooks: TaskHooks,
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "time"), allow(dead_code))]
|
||||||
|
/// Timer flavor used by the runtime
|
||||||
|
pub(crate) timer_flavor: TimerFlavor,
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
/// Indicates that the runtime is shutting down.
|
||||||
|
pub(crate) is_shutdown: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle {
|
impl Handle {
|
||||||
@@ -50,8 +61,16 @@ impl Handle {
|
|||||||
Self::bind_new_task(me, future, id, spawned_at)
|
Self::bind_new_task(me, future, id, spawned_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
pub(crate) fn is_shutdown(&self) -> bool {
|
||||||
|
self.is_shutdown
|
||||||
|
.load(crate::loom::sync::atomic::Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn shutdown(&self) {
|
pub(crate) fn shutdown(&self) {
|
||||||
self.close();
|
self.close();
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
self.is_shutdown.store(true, SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ use crate::loom::sync::Arc;
|
|||||||
use crate::runtime::{
|
use crate::runtime::{
|
||||||
blocking,
|
blocking,
|
||||||
driver::{self, Driver},
|
driver::{self, Driver},
|
||||||
scheduler, Config,
|
scheduler, Config, TimerFlavor,
|
||||||
};
|
};
|
||||||
use crate::util::RngSeedGenerator;
|
use crate::util::RngSeedGenerator;
|
||||||
|
|
||||||
@@ -61,6 +61,7 @@ impl MultiThread {
|
|||||||
blocking_spawner: blocking::Spawner,
|
blocking_spawner: blocking::Spawner,
|
||||||
seed_generator: RngSeedGenerator,
|
seed_generator: RngSeedGenerator,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
timer_flavor: TimerFlavor,
|
||||||
) -> (MultiThread, Arc<Handle>, Launch) {
|
) -> (MultiThread, Arc<Handle>, Launch) {
|
||||||
let parker = Parker::new(driver);
|
let parker = Parker::new(driver);
|
||||||
let (handle, launch) = worker::create(
|
let (handle, launch) = worker::create(
|
||||||
@@ -70,6 +71,7 @@ impl MultiThread {
|
|||||||
blocking_spawner,
|
blocking_spawner,
|
||||||
seed_generator,
|
seed_generator,
|
||||||
config,
|
config,
|
||||||
|
timer_flavor,
|
||||||
);
|
);
|
||||||
|
|
||||||
(MultiThread, handle, launch)
|
(MultiThread, handle, launch)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::runtime::driver::{self, Driver};
|
|||||||
use crate::util::TryLock;
|
use crate::util::TryLock;
|
||||||
|
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[cfg(loom)]
|
#[cfg(loom)]
|
||||||
use crate::runtime::park::CURRENT_THREAD_PARK_COUNT;
|
use crate::runtime::park::CURRENT_THREAD_PARK_COUNT;
|
||||||
@@ -70,12 +70,16 @@ impl Parker {
|
|||||||
self.inner.park(handle);
|
self.inner.park(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parks the current thread for up to `duration`.
|
||||||
|
///
|
||||||
|
/// This function tries to acquire the driver lock. If it succeeds, it
|
||||||
|
/// parks using the driver. Otherwise, it fails back to using a condvar,
|
||||||
|
/// unless the duration is zero, in which case it returns immediately.
|
||||||
pub(crate) fn park_timeout(&mut self, handle: &driver::Handle, duration: Duration) {
|
pub(crate) fn park_timeout(&mut self, handle: &driver::Handle, duration: Duration) {
|
||||||
// Only parking with zero is supported...
|
|
||||||
assert_eq!(duration, Duration::from_millis(0));
|
|
||||||
|
|
||||||
if let Some(mut driver) = self.inner.shared.driver.try_lock() {
|
if let Some(mut driver) = self.inner.shared.driver.try_lock() {
|
||||||
driver.park_timeout(handle, duration);
|
self.inner.park_driver(&mut driver, handle, Some(duration));
|
||||||
|
} else if !duration.is_zero() {
|
||||||
|
self.inner.park_condvar(Some(duration));
|
||||||
} else {
|
} else {
|
||||||
// https://github.com/tokio-rs/tokio/issues/6536
|
// https://github.com/tokio-rs/tokio/issues/6536
|
||||||
// Hacky, but it's just for loom tests. The counter gets incremented during
|
// Hacky, but it's just for loom tests. The counter gets incremented during
|
||||||
@@ -124,13 +128,20 @@ impl Inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut driver) = self.shared.driver.try_lock() {
|
if let Some(mut driver) = self.shared.driver.try_lock() {
|
||||||
self.park_driver(&mut driver, handle);
|
self.park_driver(&mut driver, handle, None);
|
||||||
} else {
|
} else {
|
||||||
self.park_condvar();
|
self.park_condvar(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn park_condvar(&self) {
|
/// Parks the current thread using a condvar for up to `duration`.
|
||||||
|
///
|
||||||
|
/// If `duration` is `None`, parks indefinitely until notified.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `duration` is `Some` and the duration is zero.
|
||||||
|
fn park_condvar(&self, duration: Option<Duration>) {
|
||||||
// Otherwise we need to coordinate going to sleep
|
// Otherwise we need to coordinate going to sleep
|
||||||
let mut m = self.mutex.lock();
|
let mut m = self.mutex.lock();
|
||||||
|
|
||||||
@@ -154,10 +165,40 @@ impl Inner {
|
|||||||
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
|
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
let timeout_at = duration.map(|d| {
|
||||||
m = self.condvar.wait(m).unwrap();
|
Instant::now()
|
||||||
|
.checked_add(d)
|
||||||
|
// best effort to avoid overflow and still provide a usable timeout
|
||||||
|
.unwrap_or(Instant::now() + Duration::from_secs(1))
|
||||||
|
});
|
||||||
|
|
||||||
if self
|
loop {
|
||||||
|
let is_timeout;
|
||||||
|
(m, is_timeout) = match timeout_at {
|
||||||
|
Some(timeout_at) => {
|
||||||
|
let dur = timeout_at.saturating_duration_since(Instant::now());
|
||||||
|
if !dur.is_zero() {
|
||||||
|
// Ideally, we would use `condvar.wait_timeout_until` here, but it is not available
|
||||||
|
// in `loom`. So we manually compute the timeout.
|
||||||
|
let (m, res) = self.condvar.wait_timeout(m, dur).unwrap();
|
||||||
|
(m, res.timed_out())
|
||||||
|
} else {
|
||||||
|
(m, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (self.condvar.wait(m).unwrap(), false),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_timeout {
|
||||||
|
match self.state.swap(EMPTY, SeqCst) {
|
||||||
|
PARKED_CONDVAR => return, // timed out, and no notification received
|
||||||
|
NOTIFIED => return, // notification and timeout happened concurrently
|
||||||
|
actual @ (PARKED_DRIVER | EMPTY) => {
|
||||||
|
panic!("inconsistent park_timeout state, actual = {actual}")
|
||||||
|
}
|
||||||
|
invalid => panic!("invalid park_timeout state, actual = {invalid}"),
|
||||||
|
}
|
||||||
|
} else if self
|
||||||
.state
|
.state
|
||||||
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
|
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -170,7 +211,19 @@ impl Inner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn park_driver(&self, driver: &mut Driver, handle: &driver::Handle) {
|
fn park_driver(
|
||||||
|
&self,
|
||||||
|
driver: &mut Driver,
|
||||||
|
handle: &driver::Handle,
|
||||||
|
duration: Option<Duration>,
|
||||||
|
) {
|
||||||
|
if duration.as_ref().is_some_and(Duration::is_zero) {
|
||||||
|
// zero duration doesn't actually park the thread, it just
|
||||||
|
// polls the I/O events, timers, etc.
|
||||||
|
driver.park_timeout(handle, Duration::ZERO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.state
|
.state
|
||||||
.compare_exchange(EMPTY, PARKED_DRIVER, SeqCst, SeqCst)
|
.compare_exchange(EMPTY, PARKED_DRIVER, SeqCst, SeqCst)
|
||||||
@@ -191,7 +244,12 @@ impl Inner {
|
|||||||
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
|
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
driver.park(handle);
|
if let Some(duration) = duration {
|
||||||
|
debug_assert_ne!(duration, Duration::ZERO);
|
||||||
|
driver.park_timeout(handle, duration);
|
||||||
|
} else {
|
||||||
|
driver.park(handle);
|
||||||
|
}
|
||||||
|
|
||||||
match self.state.swap(EMPTY, SeqCst) {
|
match self.state.swap(EMPTY, SeqCst) {
|
||||||
NOTIFIED => {} // got a notification, hurray!
|
NOTIFIED => {} // got a notification, hurray!
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ use crate::runtime::scheduler::multi_thread::{
|
|||||||
};
|
};
|
||||||
use crate::runtime::scheduler::{inject, Defer, Lock};
|
use crate::runtime::scheduler::{inject, Defer, Lock};
|
||||||
use crate::runtime::task::OwnedTasks;
|
use crate::runtime::task::OwnedTasks;
|
||||||
use crate::runtime::{blocking, driver, scheduler, task, Config, SchedulerMetrics, WorkerMetrics};
|
use crate::runtime::{
|
||||||
|
blocking, driver, scheduler, task, Config, SchedulerMetrics, TimerFlavor, WorkerMetrics,
|
||||||
|
};
|
||||||
use crate::runtime::{context, TaskHooks};
|
use crate::runtime::{context, TaskHooks};
|
||||||
use crate::task::coop;
|
use crate::task::coop;
|
||||||
use crate::util::atomic_cell::AtomicCell;
|
use crate::util::atomic_cell::AtomicCell;
|
||||||
@@ -84,6 +86,15 @@ cfg_not_taskdump! {
|
|||||||
mod taskdump_mock;
|
mod taskdump_mock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
use crate::loom::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
use crate::runtime::time_alt;
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
use crate::runtime::scheduler::util;
|
||||||
|
|
||||||
/// A scheduler worker
|
/// A scheduler worker
|
||||||
pub(super) struct Worker {
|
pub(super) struct Worker {
|
||||||
/// Reference to scheduler's handle
|
/// Reference to scheduler's handle
|
||||||
@@ -115,6 +126,9 @@ struct Core {
|
|||||||
/// The worker-local run queue.
|
/// The worker-local run queue.
|
||||||
run_queue: queue::Local<Arc<Handle>>,
|
run_queue: queue::Local<Arc<Handle>>,
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
time_context: time_alt::LocalContext,
|
||||||
|
|
||||||
/// True if the worker is currently searching for more work. Searching
|
/// True if the worker is currently searching for more work. Searching
|
||||||
/// involves attempting to steal from other workers.
|
/// involves attempting to steal from other workers.
|
||||||
is_searching: bool,
|
is_searching: bool,
|
||||||
@@ -193,6 +207,12 @@ pub(crate) struct Synced {
|
|||||||
|
|
||||||
/// Synchronized state for `Inject`.
|
/// Synchronized state for `Inject`.
|
||||||
pub(crate) inject: inject::Synced,
|
pub(crate) inject: inject::Synced,
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
/// Timers pending to be registered.
|
||||||
|
/// This is used to register a timer but the [`Core`]
|
||||||
|
/// is not available in the current thread.
|
||||||
|
inject_timers: Vec<time_alt::EntryHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to communicate with a worker from other threads.
|
/// Used to communicate with a worker from other threads.
|
||||||
@@ -241,6 +261,7 @@ pub(super) fn create(
|
|||||||
blocking_spawner: blocking::Spawner,
|
blocking_spawner: blocking::Spawner,
|
||||||
seed_generator: RngSeedGenerator,
|
seed_generator: RngSeedGenerator,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
timer_flavor: TimerFlavor,
|
||||||
) -> (Arc<Handle>, Launch) {
|
) -> (Arc<Handle>, Launch) {
|
||||||
let mut cores = Vec::with_capacity(size);
|
let mut cores = Vec::with_capacity(size);
|
||||||
let mut remotes = Vec::with_capacity(size);
|
let mut remotes = Vec::with_capacity(size);
|
||||||
@@ -260,6 +281,8 @@ pub(super) fn create(
|
|||||||
lifo_slot: None,
|
lifo_slot: None,
|
||||||
lifo_enabled: !config.disable_lifo_slot,
|
lifo_enabled: !config.disable_lifo_slot,
|
||||||
run_queue,
|
run_queue,
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
time_context: time_alt::LocalContext::new(),
|
||||||
is_searching: false,
|
is_searching: false,
|
||||||
is_shutdown: false,
|
is_shutdown: false,
|
||||||
is_traced: false,
|
is_traced: false,
|
||||||
@@ -287,6 +310,8 @@ pub(super) fn create(
|
|||||||
synced: Mutex::new(Synced {
|
synced: Mutex::new(Synced {
|
||||||
idle: idle_synced,
|
idle: idle_synced,
|
||||||
inject: inject_synced,
|
inject: inject_synced,
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
inject_timers: Vec::new(),
|
||||||
}),
|
}),
|
||||||
shutdown_cores: Mutex::new(vec![]),
|
shutdown_cores: Mutex::new(vec![]),
|
||||||
trace_status: TraceStatus::new(remotes_len),
|
trace_status: TraceStatus::new(remotes_len),
|
||||||
@@ -298,6 +323,9 @@ pub(super) fn create(
|
|||||||
driver: driver_handle,
|
driver: driver_handle,
|
||||||
blocking_spawner,
|
blocking_spawner,
|
||||||
seed_generator,
|
seed_generator,
|
||||||
|
timer_flavor,
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
is_shutdown: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut launch = Launch(vec![]);
|
let mut launch = Launch(vec![]);
|
||||||
@@ -552,7 +580,7 @@ impl Context {
|
|||||||
} else {
|
} else {
|
||||||
// Wait for work
|
// Wait for work
|
||||||
core = if !self.defer.is_empty() {
|
core = if !self.defer.is_empty() {
|
||||||
self.park_timeout(core, Some(Duration::from_millis(0)))
|
self.park_yield(core)
|
||||||
} else {
|
} else {
|
||||||
self.park(core)
|
self.park(core)
|
||||||
};
|
};
|
||||||
@@ -560,6 +588,21 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
{
|
||||||
|
match self.worker.handle.timer_flavor {
|
||||||
|
TimerFlavor::Traditional => {}
|
||||||
|
TimerFlavor::Alternative => {
|
||||||
|
util::time_alt::shutdown_local_timers(
|
||||||
|
&mut core.time_context.wheel,
|
||||||
|
&mut core.time_context.canc_rx,
|
||||||
|
self.worker.handle.take_remote_timers(),
|
||||||
|
&self.worker.handle.driver,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
core.pre_shutdown(&self.worker);
|
core.pre_shutdown(&self.worker);
|
||||||
// Signal shutdown
|
// Signal shutdown
|
||||||
self.worker.handle.shutdown_core(core);
|
self.worker.handle.shutdown_core(core);
|
||||||
@@ -701,7 +744,7 @@ impl Context {
|
|||||||
|
|
||||||
// Call `park` with a 0 timeout. This enables the I/O driver, timer, ...
|
// Call `park` with a 0 timeout. This enables the I/O driver, timer, ...
|
||||||
// to run without actually putting the thread to sleep.
|
// to run without actually putting the thread to sleep.
|
||||||
core = self.park_timeout(core, Some(Duration::from_millis(0)));
|
core = self.park_yield(core);
|
||||||
|
|
||||||
// Run regularly scheduled maintenance
|
// Run regularly scheduled maintenance
|
||||||
core.maintenance(&self.worker);
|
core.maintenance(&self.worker);
|
||||||
@@ -734,7 +777,7 @@ impl Context {
|
|||||||
core.stats
|
core.stats
|
||||||
.submit(&self.worker.handle.shared.worker_metrics[self.worker.index]);
|
.submit(&self.worker.handle.shared.worker_metrics[self.worker.index]);
|
||||||
|
|
||||||
core = self.park_timeout(core, None);
|
core = self.park_internal(core, None);
|
||||||
|
|
||||||
core.stats.unparked();
|
core.stats.unparked();
|
||||||
|
|
||||||
@@ -753,15 +796,35 @@ impl Context {
|
|||||||
core
|
core
|
||||||
}
|
}
|
||||||
|
|
||||||
fn park_timeout(&self, mut core: Box<Core>, duration: Option<Duration>) -> Box<Core> {
|
fn park_yield(&self, core: Box<Core>) -> Box<Core> {
|
||||||
|
self.park_internal(core, Some(Duration::from_millis(0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn park_internal(&self, mut core: Box<Core>, duration: Option<Duration>) -> Box<Core> {
|
||||||
self.assert_lifo_enabled_is_correct(&core);
|
self.assert_lifo_enabled_is_correct(&core);
|
||||||
|
|
||||||
// Take the parker out of core
|
// Take the parker out of core
|
||||||
let mut park = core.park.take().expect("park missing");
|
let mut park = core.park.take().expect("park missing");
|
||||||
|
|
||||||
// Store `core` in context
|
// Store `core` in context
|
||||||
*self.core.borrow_mut() = Some(core);
|
*self.core.borrow_mut() = Some(core);
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
let (duration, auto_advance_duration) = match self.worker.handle.timer_flavor {
|
||||||
|
TimerFlavor::Traditional => (duration, None::<Duration>),
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
TimerFlavor::Alternative => {
|
||||||
|
// Must happens after taking out the parker, as the `Handle::schedule_local`
|
||||||
|
// will delay the notify if the parker taken out.
|
||||||
|
//
|
||||||
|
// See comments in `Handle::schedule_local` for more details.
|
||||||
|
let MaintainLocalTimer {
|
||||||
|
park_duration: duration,
|
||||||
|
auto_advance_duration,
|
||||||
|
} = self.maintain_local_timers_before_parking(duration);
|
||||||
|
(duration, auto_advance_duration)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Park thread
|
// Park thread
|
||||||
if let Some(timeout) = duration {
|
if let Some(timeout) = duration {
|
||||||
park.park_timeout(&self.worker.handle.driver, timeout);
|
park.park_timeout(&self.worker.handle.driver, timeout);
|
||||||
@@ -771,16 +834,30 @@ impl Context {
|
|||||||
|
|
||||||
self.defer.wake();
|
self.defer.wake();
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
match self.worker.handle.timer_flavor {
|
||||||
|
TimerFlavor::Traditional => {
|
||||||
|
// suppress unused variable warning
|
||||||
|
let _ = auto_advance_duration;
|
||||||
|
}
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
TimerFlavor::Alternative => {
|
||||||
|
// Must happens before placing back the parker, as the `Handle::schedule_local`
|
||||||
|
// will delay the notify if the parker is still in `core`.
|
||||||
|
//
|
||||||
|
// See comments in `Handle::schedule_local` for more details.
|
||||||
|
self.maintain_local_timers_after_parking(auto_advance_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove `core` from context
|
// Remove `core` from context
|
||||||
core = self.core.borrow_mut().take().expect("core missing");
|
core = self.core.borrow_mut().take().expect("core missing");
|
||||||
|
|
||||||
// Place `park` back in `core`
|
// Place `park` back in `core`
|
||||||
core.park = Some(park);
|
core.park = Some(park);
|
||||||
|
|
||||||
if core.should_notify_others() {
|
if core.should_notify_others() {
|
||||||
self.worker.handle.notify_parked_local();
|
self.worker.handle.notify_parked_local();
|
||||||
}
|
}
|
||||||
|
|
||||||
core
|
core
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,6 +870,138 @@ impl Context {
|
|||||||
self.defer.defer(waker);
|
self.defer.defer(waker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
/// Maintain local timers before parking the resource driver.
|
||||||
|
///
|
||||||
|
/// * Remove cancelled timers from the local timer wheel.
|
||||||
|
/// * Register remote timers to the local timer wheel.
|
||||||
|
/// * Adjust the park duration based on
|
||||||
|
/// * the next timer expiration time.
|
||||||
|
/// * whether auto-advancing is required (feature = "test-util").
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `(Box<Core>, park_duration, auto_advance_duration)`
|
||||||
|
fn maintain_local_timers_before_parking(
|
||||||
|
&self,
|
||||||
|
park_duration: Option<Duration>,
|
||||||
|
) -> MaintainLocalTimer {
|
||||||
|
let handle = &self.worker.handle;
|
||||||
|
let mut wake_queue = time_alt::WakeQueue::new();
|
||||||
|
|
||||||
|
let (should_yield, next_timer) = with_current(|maybe_cx| {
|
||||||
|
let cx = maybe_cx.expect("function should be called when core is present");
|
||||||
|
assert_eq!(
|
||||||
|
Arc::as_ptr(&cx.worker.handle),
|
||||||
|
Arc::as_ptr(&self.worker.handle),
|
||||||
|
"function should be called on the exact same worker"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut maybe_core = cx.core.borrow_mut();
|
||||||
|
let core = maybe_core.as_mut().expect("core missing");
|
||||||
|
let time_cx = &mut core.time_context;
|
||||||
|
|
||||||
|
util::time_alt::process_registration_queue(
|
||||||
|
&mut time_cx.registration_queue,
|
||||||
|
&mut time_cx.wheel,
|
||||||
|
&time_cx.canc_tx,
|
||||||
|
&mut wake_queue,
|
||||||
|
);
|
||||||
|
util::time_alt::insert_inject_timers(
|
||||||
|
&mut time_cx.wheel,
|
||||||
|
&time_cx.canc_tx,
|
||||||
|
handle.take_remote_timers(),
|
||||||
|
&mut wake_queue,
|
||||||
|
);
|
||||||
|
util::time_alt::remove_cancelled_timers(&mut time_cx.wheel, &mut time_cx.canc_rx);
|
||||||
|
let should_yield = !wake_queue.is_empty();
|
||||||
|
|
||||||
|
let next_timer = util::time_alt::next_expiration_time(&time_cx.wheel, &handle.driver);
|
||||||
|
|
||||||
|
(should_yield, next_timer)
|
||||||
|
});
|
||||||
|
|
||||||
|
wake_queue.wake_all();
|
||||||
|
|
||||||
|
if should_yield {
|
||||||
|
MaintainLocalTimer {
|
||||||
|
park_duration: Some(Duration::from_millis(0)),
|
||||||
|
auto_advance_duration: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get the minimum duration
|
||||||
|
let dur = util::time_alt::min_duration(park_duration, next_timer);
|
||||||
|
if util::time_alt::pre_auto_advance(&handle.driver, dur) {
|
||||||
|
MaintainLocalTimer {
|
||||||
|
park_duration: Some(Duration::ZERO),
|
||||||
|
auto_advance_duration: dur,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MaintainLocalTimer {
|
||||||
|
park_duration: dur,
|
||||||
|
auto_advance_duration: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
/// Maintain local timers after unparking the resource driver.
|
||||||
|
///
|
||||||
|
/// * Auto-advance time, if required (feature = "test-util").
|
||||||
|
/// * Process expired timers.
|
||||||
|
fn maintain_local_timers_after_parking(&self, auto_advance_duration: Option<Duration>) {
|
||||||
|
let handle = &self.worker.handle;
|
||||||
|
let mut wake_queue = time_alt::WakeQueue::new();
|
||||||
|
|
||||||
|
with_current(|maybe_cx| {
|
||||||
|
let cx = maybe_cx.expect("function should be called when core is present");
|
||||||
|
assert_eq!(
|
||||||
|
Arc::as_ptr(&cx.worker.handle),
|
||||||
|
Arc::as_ptr(&self.worker.handle),
|
||||||
|
"function should be called on the exact same worker"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut maybe_core = cx.core.borrow_mut();
|
||||||
|
let core = maybe_core.as_mut().expect("core missing");
|
||||||
|
let time_cx = &mut core.time_context;
|
||||||
|
|
||||||
|
util::time_alt::post_auto_advance(&handle.driver, auto_advance_duration);
|
||||||
|
util::time_alt::process_expired_timers(
|
||||||
|
&mut time_cx.wheel,
|
||||||
|
&handle.driver,
|
||||||
|
&mut wake_queue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
wake_queue.wake_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
fn with_core<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<&mut Core>) -> R,
|
||||||
|
{
|
||||||
|
match self.core.borrow_mut().as_mut() {
|
||||||
|
Some(core) => f(Some(core)),
|
||||||
|
None => f(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
pub(crate) fn with_time_temp_local_context<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<time_alt::TempLocalContext<'_>>) -> R,
|
||||||
|
{
|
||||||
|
self.with_core(|maybe_core| match maybe_core {
|
||||||
|
Some(core) if core.is_shutdown => f(Some(time_alt::TempLocalContext::new_shutdown())),
|
||||||
|
Some(core) => f(Some(time_alt::TempLocalContext::new_running(
|
||||||
|
&mut core.time_context,
|
||||||
|
))),
|
||||||
|
None => f(None),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
@@ -1131,6 +1340,27 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
pub(crate) fn push_remote_timer(&self, hdl: time_alt::EntryHandle) {
|
||||||
|
assert_eq!(self.timer_flavor, TimerFlavor::Alternative);
|
||||||
|
{
|
||||||
|
let mut synced = self.shared.synced.lock();
|
||||||
|
synced.inject_timers.push(hdl);
|
||||||
|
}
|
||||||
|
self.notify_parked_remote();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
pub(crate) fn take_remote_timers(&self) -> Vec<time_alt::EntryHandle> {
|
||||||
|
assert_eq!(self.timer_flavor, TimerFlavor::Alternative);
|
||||||
|
// It's ok to lost the race, as another worker is
|
||||||
|
// draining the inject_timers.
|
||||||
|
match self.shared.synced.try_lock() {
|
||||||
|
Some(mut synced) => std::mem::take(&mut synced.inject_timers),
|
||||||
|
None => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn close(&self) {
|
pub(super) fn close(&self) {
|
||||||
if self
|
if self
|
||||||
.shared
|
.shared
|
||||||
@@ -1249,6 +1479,13 @@ impl<'a> Lock<inject::Synced> for &'a Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "time"))]
|
||||||
|
/// Returned by [`Context::maintain_local_timers_before_parking`].
|
||||||
|
struct MaintainLocalTimer {
|
||||||
|
park_duration: Option<Duration>,
|
||||||
|
auto_advance_duration: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn with_current<R>(f: impl FnOnce(Option<&Context>) -> R) -> R {
|
fn with_current<R>(f: impl FnOnce(Option<&Context>) -> R) -> R {
|
||||||
use scheduler::Context::MultiThread;
|
use scheduler::Context::MultiThread;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
|
||||||
|
pub(in crate::runtime) mod time_alt;
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
use crate::runtime::scheduler::driver;
|
||||||
|
use crate::runtime::time_alt::cancellation_queue::{Receiver, Sender};
|
||||||
|
use crate::runtime::time_alt::{EntryHandle, RegistrationQueue, WakeQueue, Wheel};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub(crate) fn min_duration(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
|
||||||
|
match (a, b) {
|
||||||
|
(Some(dur_a), Some(dur_b)) => Some(std::cmp::min(dur_a, dur_b)),
|
||||||
|
(Some(dur_a), None) => Some(dur_a),
|
||||||
|
(None, Some(dur_b)) => Some(dur_b),
|
||||||
|
(None, None) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn process_registration_queue(
|
||||||
|
registration_queue: &mut RegistrationQueue,
|
||||||
|
wheel: &mut Wheel,
|
||||||
|
tx: &Sender,
|
||||||
|
wake_queue: &mut WakeQueue,
|
||||||
|
) {
|
||||||
|
while let Some(hdl) = registration_queue.pop_front() {
|
||||||
|
if hdl.deadline() <= wheel.elapsed() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Safety: the entry is not registered yet
|
||||||
|
unsafe {
|
||||||
|
wheel.insert(hdl, tx.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert_inject_timers(
|
||||||
|
wheel: &mut Wheel,
|
||||||
|
tx: &Sender,
|
||||||
|
inject: Vec<EntryHandle>,
|
||||||
|
wake_queue: &mut WakeQueue,
|
||||||
|
) {
|
||||||
|
for hdl in inject {
|
||||||
|
if hdl.deadline() <= wheel.elapsed() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Safety: the entry is not registered yet
|
||||||
|
unsafe {
|
||||||
|
wheel.insert(hdl, tx.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remove_cancelled_timers(wheel: &mut Wheel, rx: &mut Receiver) {
|
||||||
|
for hdl in rx.recv_all() {
|
||||||
|
debug_assert!(hdl.is_cancelled());
|
||||||
|
|
||||||
|
if hdl.deadline() > wheel.elapsed() {
|
||||||
|
// Safety: the entry is registered in THIS wheel
|
||||||
|
unsafe {
|
||||||
|
wheel.remove(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn next_expiration_time(wheel: &Wheel, drv_hdl: &driver::Handle) -> Option<Duration> {
|
||||||
|
drv_hdl.with_time(|maybe_time_hdl| {
|
||||||
|
let Some(time_hdl) = maybe_time_hdl else {
|
||||||
|
// time driver is not enabled, nothing to do.
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let clock = drv_hdl.clock();
|
||||||
|
let time_source = time_hdl.time_source();
|
||||||
|
|
||||||
|
wheel.next_expiration_time().map(|tick| {
|
||||||
|
let now = time_source.now(clock);
|
||||||
|
time_source.tick_to_duration(tick.saturating_sub(now))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test-util")]
|
||||||
|
pub(crate) fn pre_auto_advance(drv_hdl: &driver::Handle, duration: Option<Duration>) -> bool {
|
||||||
|
drv_hdl.with_time(|maybe_time_hdl| {
|
||||||
|
if maybe_time_hdl.is_none() {
|
||||||
|
// time driver is not enabled, nothing to do.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration.is_some() {
|
||||||
|
let clock = drv_hdl.clock();
|
||||||
|
if clock.can_auto_advance() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn process_expired_timers(
|
||||||
|
wheel: &mut Wheel,
|
||||||
|
drv_hdl: &driver::Handle,
|
||||||
|
wake_queue: &mut WakeQueue,
|
||||||
|
) {
|
||||||
|
drv_hdl.with_time(|maybe_time_hdl| {
|
||||||
|
let Some(time_hdl) = maybe_time_hdl else {
|
||||||
|
// time driver is not enabled, nothing to do.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let clock = drv_hdl.clock();
|
||||||
|
let time_source = time_hdl.time_source();
|
||||||
|
|
||||||
|
let now = time_source.now(clock);
|
||||||
|
time_hdl.process_at_time_alt(wheel, now, wake_queue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shutdown_local_timers(
|
||||||
|
wheel: &mut Wheel,
|
||||||
|
rx: &mut Receiver,
|
||||||
|
inject: Vec<EntryHandle>,
|
||||||
|
drv_hdl: &driver::Handle,
|
||||||
|
) {
|
||||||
|
drv_hdl.with_time(|maybe_time_hdl| {
|
||||||
|
let Some(time_hdl) = maybe_time_hdl else {
|
||||||
|
// time driver is not enabled, nothing to do.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
remove_cancelled_timers(wheel, rx);
|
||||||
|
time_hdl.shutdown_alt(wheel);
|
||||||
|
|
||||||
|
let mut wake_queue = WakeQueue::new();
|
||||||
|
// simply wake all unregistered timers
|
||||||
|
for hdl in inject {
|
||||||
|
if !hdl.is_cancelled() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wake_queue.wake_all();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test-util")]
|
||||||
|
pub(crate) fn post_auto_advance(drv_hdl: &driver::Handle, duration: Option<Duration>) {
|
||||||
|
drv_hdl.with_time(|maybe_time_hdl| {
|
||||||
|
let Some(time_hdl) = maybe_time_hdl else {
|
||||||
|
// time driver is not enabled, nothing to do.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(park_duration) = duration {
|
||||||
|
let clock = drv_hdl.clock();
|
||||||
|
if clock.can_auto_advance() && !time_hdl.did_wake() {
|
||||||
|
if let Err(msg) = clock.advance(park_duration) {
|
||||||
|
panic!("{msg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "test-util"))]
|
||||||
|
pub(crate) fn pre_auto_advance(_drv_hdl: &driver::Handle, _duration: Option<Duration>) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "test-util"))]
|
||||||
|
pub(crate) fn post_auto_advance(_drv_hdl: &driver::Handle, _duration: Option<Duration>) {
|
||||||
|
// No-op in non-test util builds
|
||||||
|
}
|
||||||
@@ -21,9 +21,15 @@ impl Handle {
|
|||||||
/// Track that the driver is being unparked
|
/// Track that the driver is being unparked
|
||||||
pub(crate) fn unpark(&self) {
|
pub(crate) fn unpark(&self) {
|
||||||
#[cfg(feature = "test-util")]
|
#[cfg(feature = "test-util")]
|
||||||
self.inner
|
match self.inner {
|
||||||
.did_wake
|
super::Inner::Traditional { ref did_wake, .. } => {
|
||||||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
did_wake.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
super::Inner::Alternative { ref did_wake, .. } => {
|
||||||
|
did_wake.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+104
-20
@@ -18,6 +18,9 @@ pub(crate) use source::TimeSource;
|
|||||||
|
|
||||||
mod wheel;
|
mod wheel;
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
use super::time_alt;
|
||||||
|
|
||||||
use crate::loom::sync::atomic::{AtomicBool, Ordering};
|
use crate::loom::sync::atomic::{AtomicBool, Ordering};
|
||||||
use crate::loom::sync::Mutex;
|
use crate::loom::sync::Mutex;
|
||||||
use crate::runtime::driver::{self, IoHandle, IoStack};
|
use crate::runtime::driver::{self, IoHandle, IoStack};
|
||||||
@@ -89,22 +92,38 @@ pub(crate) struct Driver {
|
|||||||
park: IoStack,
|
park: IoStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Timer state shared between `Driver`, `Handle`, and `Registration`.
|
enum Inner {
|
||||||
struct Inner {
|
Traditional {
|
||||||
// The state is split like this so `Handle` can access `is_shutdown` without locking the mutex
|
// The state is split like this so `Handle` can access `is_shutdown` without locking the mutex
|
||||||
state: Mutex<InnerState>,
|
state: Mutex<InnerState>,
|
||||||
|
|
||||||
/// True if the driver is being shutdown.
|
/// True if the driver is being shutdown.
|
||||||
is_shutdown: AtomicBool,
|
is_shutdown: AtomicBool,
|
||||||
|
|
||||||
// When `true`, a call to `park_timeout` should immediately return and time
|
// When `true`, a call to `park_timeout` should immediately return and time
|
||||||
// should not advance. One reason for this to be `true` is if the task
|
// should not advance. One reason for this to be `true` is if the task
|
||||||
// passed to `Runtime::block_on` called `task::yield_now()`.
|
// passed to `Runtime::block_on` called `task::yield_now()`.
|
||||||
//
|
//
|
||||||
// While it may look racy, it only has any effect when the clock is paused
|
// While it may look racy, it only has any effect when the clock is paused
|
||||||
// and pausing the clock is restricted to a single-threaded runtime.
|
// and pausing the clock is restricted to a single-threaded runtime.
|
||||||
#[cfg(feature = "test-util")]
|
#[cfg(feature = "test-util")]
|
||||||
did_wake: AtomicBool,
|
did_wake: AtomicBool,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Alternative {
|
||||||
|
/// True if the driver is being shutdown.
|
||||||
|
is_shutdown: AtomicBool,
|
||||||
|
|
||||||
|
// When `true`, a call to `park_timeout` should immediately return and time
|
||||||
|
// should not advance. One reason for this to be `true` is if the task
|
||||||
|
// passed to `Runtime::block_on` called `task::yield_now()`.
|
||||||
|
//
|
||||||
|
// While it may look racy, it only has any effect when the clock is paused
|
||||||
|
// and pausing the clock is restricted to a single-threaded runtime.
|
||||||
|
#[cfg(feature = "test-util")]
|
||||||
|
did_wake: AtomicBool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Time state shared which must be protected by a `Mutex`
|
/// Time state shared which must be protected by a `Mutex`
|
||||||
@@ -128,7 +147,7 @@ impl Driver {
|
|||||||
|
|
||||||
let handle = Handle {
|
let handle = Handle {
|
||||||
time_source,
|
time_source,
|
||||||
inner: Inner {
|
inner: Inner::Traditional {
|
||||||
state: Mutex::new(InnerState {
|
state: Mutex::new(InnerState {
|
||||||
next_wake: None,
|
next_wake: None,
|
||||||
wheel: wheel::Wheel::new(),
|
wheel: wheel::Wheel::new(),
|
||||||
@@ -145,6 +164,20 @@ impl Driver {
|
|||||||
(driver, handle)
|
(driver, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) fn new_alt(clock: &Clock) -> Handle {
|
||||||
|
let time_source = TimeSource::new(clock);
|
||||||
|
|
||||||
|
Handle {
|
||||||
|
time_source,
|
||||||
|
inner: Inner::Alternative {
|
||||||
|
is_shutdown: AtomicBool::new(false),
|
||||||
|
#[cfg(feature = "test-util")]
|
||||||
|
did_wake: AtomicBool::new(false),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn park(&mut self, handle: &driver::Handle) {
|
pub(crate) fn park(&mut self, handle: &driver::Handle) {
|
||||||
self.park_internal(handle, None);
|
self.park_internal(handle, None);
|
||||||
}
|
}
|
||||||
@@ -160,7 +193,15 @@ impl Driver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.inner.is_shutdown.store(true, Ordering::SeqCst);
|
match &handle.inner {
|
||||||
|
Inner::Traditional { is_shutdown, .. } => {
|
||||||
|
is_shutdown.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Inner::Alternative { is_shutdown, .. } => {
|
||||||
|
is_shutdown.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Advance time forward to the end of time.
|
// Advance time forward to the end of time.
|
||||||
|
|
||||||
@@ -295,6 +336,37 @@ impl Handle {
|
|||||||
waker_list.wake_all();
|
waker_list.wake_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) fn process_at_time_alt(
|
||||||
|
&self,
|
||||||
|
wheel: &mut time_alt::Wheel,
|
||||||
|
mut now: u64,
|
||||||
|
wake_queue: &mut time_alt::WakeQueue,
|
||||||
|
) {
|
||||||
|
if now < wheel.elapsed() {
|
||||||
|
// Time went backwards! This normally shouldn't happen as the Rust language
|
||||||
|
// guarantees that an Instant is monotonic, but can happen when running
|
||||||
|
// Linux in a VM on a Windows host due to std incorrectly trusting the
|
||||||
|
// hardware clock to be monotonic.
|
||||||
|
//
|
||||||
|
// See <https://github.com/tokio-rs/tokio/issues/3619> for more information.
|
||||||
|
now = wheel.elapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
wheel.take_expired(now, wake_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
pub(crate) fn shutdown_alt(&self, wheel: &mut time_alt::Wheel) {
|
||||||
|
// self.is_shutdown.store(true, Ordering::SeqCst);
|
||||||
|
// Advance time forward to the end of time.
|
||||||
|
// This will ensure that all timers are fired.
|
||||||
|
let max_tick = u64::MAX;
|
||||||
|
let mut wake_queue = time_alt::WakeQueue::new();
|
||||||
|
self.process_at_time_alt(wheel, max_tick, &mut wake_queue);
|
||||||
|
wake_queue.wake_all();
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes a registered timer from the driver.
|
/// Removes a registered timer from the driver.
|
||||||
///
|
///
|
||||||
/// The timer will be moved to the cancelled state. Wakers will _not_ be
|
/// The timer will be moved to the cancelled state. Wakers will _not_ be
|
||||||
@@ -379,8 +451,12 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg_test_util! {
|
cfg_test_util! {
|
||||||
fn did_wake(&self) -> bool {
|
pub(super) fn did_wake(&self) -> bool {
|
||||||
self.inner.did_wake.swap(false, Ordering::SeqCst)
|
match &self.inner {
|
||||||
|
Inner::Traditional { did_wake, .. } => did_wake.swap(false, Ordering::SeqCst),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Inner::Alternative { did_wake, .. } => did_wake.swap(false, Ordering::SeqCst),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,12 +466,20 @@ impl Handle {
|
|||||||
impl Inner {
|
impl Inner {
|
||||||
/// Locks the driver's inner structure
|
/// Locks the driver's inner structure
|
||||||
pub(super) fn lock(&self) -> crate::loom::sync::MutexGuard<'_, InnerState> {
|
pub(super) fn lock(&self) -> crate::loom::sync::MutexGuard<'_, InnerState> {
|
||||||
self.state.lock()
|
match self {
|
||||||
|
Inner::Traditional { state, .. } => state.lock(),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Inner::Alternative { .. } => unreachable!("unreachable in alternative timer"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the driver has been shutdown
|
// Check whether the driver has been shutdown
|
||||||
pub(super) fn is_shutdown(&self) -> bool {
|
pub(super) fn is_shutdown(&self) -> bool {
|
||||||
self.is_shutdown.load(Ordering::SeqCst)
|
match self {
|
||||||
|
Inner::Traditional { is_shutdown, .. } => is_shutdown.load(Ordering::SeqCst),
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
Inner::Alternative { is_shutdown, .. } => is_shutdown.load(Ordering::SeqCst),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
use super::{CancellationQueueEntry, Entry, EntryHandle};
|
||||||
|
use crate::loom::sync::{Arc, Mutex};
|
||||||
|
use crate::util::linked_list;
|
||||||
|
|
||||||
|
type EntryList = linked_list::LinkedList<CancellationQueueEntry, Entry>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Inner {
|
||||||
|
list: EntryList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Inner {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// consume all entries
|
||||||
|
let _ = self.iter().count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
list: EntryList::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Behavior is undefined if any of the following conditions are violated:
|
||||||
|
///
|
||||||
|
/// - `hdl` must not in any [`super::cancellation_queue`], and also mus not in any [`super::WakeQueue`].
|
||||||
|
unsafe fn push_front(&mut self, hdl: EntryHandle) {
|
||||||
|
self.list.push_front(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&mut self) -> impl Iterator<Item = EntryHandle> {
|
||||||
|
struct Iter {
|
||||||
|
list: EntryList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Iter {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
while let Some(hdl) = self.list.pop_front() {
|
||||||
|
drop(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Iter {
|
||||||
|
type Item = EntryHandle;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.list.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iter {
|
||||||
|
list: std::mem::take(&mut self.list),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Sender {
|
||||||
|
inner: Arc<Mutex<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sender {
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Behavior is undefined if any of the following conditions are violated:
|
||||||
|
///
|
||||||
|
/// - `hdl` must not in any cancellation queue.
|
||||||
|
pub(crate) unsafe fn send(&self, hdl: EntryHandle) {
|
||||||
|
unsafe {
|
||||||
|
self.inner.lock().push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Receiver {
|
||||||
|
inner: Arc<Mutex<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Receiver {
|
||||||
|
pub(crate) fn recv_all(&mut self) -> impl Iterator<Item = EntryHandle> {
|
||||||
|
self.inner.lock().iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new() -> (Sender, Receiver) {
|
||||||
|
let inner = Arc::new(Mutex::new(Inner::new()));
|
||||||
|
(
|
||||||
|
Sender {
|
||||||
|
inner: inner.clone(),
|
||||||
|
},
|
||||||
|
Receiver { inner },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use futures::task::noop_waker;
|
||||||
|
|
||||||
|
#[cfg(loom)]
|
||||||
|
const NUM_ITEMS: usize = 16;
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
const NUM_ITEMS: usize = 64;
|
||||||
|
|
||||||
|
fn new_handle() -> EntryHandle {
|
||||||
|
EntryHandle::new(0, noop_waker())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn model<F: Fn() + Send + Sync + 'static>(f: F) {
|
||||||
|
#[cfg(loom)]
|
||||||
|
loom::model(f);
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_thread() {
|
||||||
|
model(|| {
|
||||||
|
for i in 0..NUM_ITEMS {
|
||||||
|
let (tx, mut rx) = new();
|
||||||
|
|
||||||
|
for _ in 0..i {
|
||||||
|
unsafe { tx.send(new_handle()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(rx.recv_all().count(), i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_os = "wasi"))] // No thread on wasi.
|
||||||
|
fn multi_thread() {
|
||||||
|
use crate::loom::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||||
|
use crate::loom::sync::Arc;
|
||||||
|
use crate::loom::thread;
|
||||||
|
|
||||||
|
#[cfg(loom)]
|
||||||
|
const NUM_THREADS: usize = 3;
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
const NUM_THREADS: usize = 8;
|
||||||
|
|
||||||
|
model(|| {
|
||||||
|
let (tx, mut rx) = new();
|
||||||
|
let mut jhs = Vec::new();
|
||||||
|
let sent = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
for _ in 0..NUM_THREADS {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let sent = sent.clone();
|
||||||
|
jhs.push(thread::spawn(move || {
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
unsafe { tx.send(new_handle()) };
|
||||||
|
sent.fetch_add(1, SeqCst);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
loop {
|
||||||
|
count += rx.recv_all().count();
|
||||||
|
if sent.fetch_add(0, SeqCst) == NUM_ITEMS * NUM_THREADS {
|
||||||
|
jhs.into_iter().for_each(|jh| {
|
||||||
|
jh.join().unwrap();
|
||||||
|
});
|
||||||
|
count += rx.recv_all().count();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
thread::yield_now();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(count, NUM_ITEMS * NUM_THREADS);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop_iter_should_not_leak_memory() {
|
||||||
|
model(|| {
|
||||||
|
let (tx, mut rx) = new();
|
||||||
|
|
||||||
|
let hdls = (0..NUM_ITEMS).map(|_| new_handle()).collect::<Vec<_>>();
|
||||||
|
for hdl in hdls.iter() {
|
||||||
|
unsafe { tx.send(hdl.clone()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(rx.recv_all());
|
||||||
|
|
||||||
|
assert!(hdls.into_iter().all(|hdl| hdl.inner_strong_count() == 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use super::{cancellation_queue, RegistrationQueue, Wheel};
|
||||||
|
|
||||||
|
/// Local context for the time driver, used when the runtime wants to
|
||||||
|
/// fire/cancel timers.
|
||||||
|
pub(crate) struct LocalContext {
|
||||||
|
pub(crate) wheel: Wheel,
|
||||||
|
pub(crate) registration_queue: RegistrationQueue,
|
||||||
|
pub(crate) canc_tx: cancellation_queue::Sender,
|
||||||
|
pub(crate) canc_rx: cancellation_queue::Receiver,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalContext {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
let (canc_tx, canc_rx) = cancellation_queue::new();
|
||||||
|
Self {
|
||||||
|
wheel: Wheel::new(),
|
||||||
|
registration_queue: RegistrationQueue::new(),
|
||||||
|
canc_tx,
|
||||||
|
canc_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum TempLocalContext<'a> {
|
||||||
|
/// The runtime is running, we can access it.
|
||||||
|
Running {
|
||||||
|
registration_queue: &'a mut RegistrationQueue,
|
||||||
|
elapsed: u64,
|
||||||
|
},
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
/// The runtime is shutting down, no timers can be registered.
|
||||||
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TempLocalContext<'a> {
|
||||||
|
pub(crate) fn new_running(cx: &'a mut LocalContext) -> Self {
|
||||||
|
TempLocalContext::Running {
|
||||||
|
registration_queue: &mut cx.registration_queue,
|
||||||
|
elapsed: cx.wheel.elapsed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
pub(crate) fn new_shutdown() -> Self {
|
||||||
|
TempLocalContext::Shutdown
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
use super::cancellation_queue::Sender;
|
||||||
|
use crate::loom::sync::{Arc, Mutex};
|
||||||
|
use crate::util::linked_list;
|
||||||
|
|
||||||
|
use std::marker::PhantomPinned;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
use std::task::Waker;
|
||||||
|
|
||||||
|
pub(super) type EntryList = linked_list::LinkedList<Entry, Entry>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct State {
|
||||||
|
cancelled: bool,
|
||||||
|
woken_up: bool,
|
||||||
|
waker: Option<Waker>,
|
||||||
|
cancel_tx: Option<Sender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Entry {
|
||||||
|
/// The intrusive pointer used by [`super::cancellation_queue`].
|
||||||
|
cancel_pointers: linked_list::Pointers<Entry>,
|
||||||
|
|
||||||
|
/// The intrusive pointer used by any of the following queues:
|
||||||
|
///
|
||||||
|
/// - [`Wheel`]
|
||||||
|
/// - [`RegistrationQueue`]
|
||||||
|
/// - [`WakeQueue`]
|
||||||
|
///
|
||||||
|
/// We can guarantee that this pointer is only used by one of the above
|
||||||
|
/// at any given time. See below for the journey of this pointer.
|
||||||
|
///
|
||||||
|
/// Initially, this pointer is used by the [`RegistrationQueue`].
|
||||||
|
///
|
||||||
|
/// And then, before parking the resource driver,
|
||||||
|
/// the scheduler removes the entry from the [`RegistrationQueue`]
|
||||||
|
/// [`RegistrationQueue`] and insert it into the [`Wheel`].
|
||||||
|
///
|
||||||
|
/// Finally, after parking the resource driver, the scheduler removes
|
||||||
|
/// the entry from the [`Wheel`] and insert it into the [`WakeQueue`].
|
||||||
|
///
|
||||||
|
/// [`RegistrationQueue`]: super::RegistrationQueue
|
||||||
|
/// [`Wheel`]: super::Wheel
|
||||||
|
/// [`WakeQueue`]: super::WakeQueue
|
||||||
|
extra_pointers: linked_list::Pointers<Entry>,
|
||||||
|
|
||||||
|
/// The tick when this entry is scheduled to expire.
|
||||||
|
deadline: u64,
|
||||||
|
|
||||||
|
state: Mutex<State>,
|
||||||
|
|
||||||
|
/// Make the type `!Unpin` to prevent LLVM from emitting
|
||||||
|
/// the `noalias` attribute for mutable references.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rust-lang/rust/pull/82834>.
|
||||||
|
_pin: PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: `Entry` is always in an `Arc`.
|
||||||
|
unsafe impl linked_list::Link for Entry {
|
||||||
|
type Handle = Handle;
|
||||||
|
type Target = Entry;
|
||||||
|
|
||||||
|
fn as_raw(hdl: &Self::Handle) -> NonNull<Self::Target> {
|
||||||
|
unsafe { NonNull::new_unchecked(Arc::as_ptr(&hdl.entry).cast_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self::Handle {
|
||||||
|
Handle {
|
||||||
|
entry: unsafe { Arc::from_raw(ptr.as_ptr()) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn pointers(
|
||||||
|
target: NonNull<Self::Target>,
|
||||||
|
) -> NonNull<linked_list::Pointers<Self::Target>> {
|
||||||
|
let this = target.as_ptr();
|
||||||
|
let field = unsafe { std::ptr::addr_of_mut!((*this).extra_pointers) };
|
||||||
|
unsafe { NonNull::new_unchecked(field) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ZST to allow [`super::registration_queue`] to utilize the [`Entry::extra_pointers`]
|
||||||
|
/// by impl [`linked_list::Link`] as we cannot impl it on [`Entry`]
|
||||||
|
/// directly due to the conflicting implementations.
|
||||||
|
///
|
||||||
|
/// This type should never be constructed.
|
||||||
|
pub(super) struct RegistrationQueueEntry;
|
||||||
|
|
||||||
|
// Safety: `Entry` is always in an `Arc`.
|
||||||
|
unsafe impl linked_list::Link for RegistrationQueueEntry {
|
||||||
|
type Handle = Handle;
|
||||||
|
type Target = Entry;
|
||||||
|
|
||||||
|
fn as_raw(hdl: &Self::Handle) -> NonNull<Self::Target> {
|
||||||
|
unsafe { NonNull::new_unchecked(Arc::as_ptr(&hdl.entry).cast_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self::Handle {
|
||||||
|
Handle {
|
||||||
|
entry: unsafe { Arc::from_raw(ptr.as_ptr()) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn pointers(
|
||||||
|
target: NonNull<Self::Target>,
|
||||||
|
) -> NonNull<linked_list::Pointers<Self::Target>> {
|
||||||
|
let this = target.as_ptr();
|
||||||
|
let field = unsafe { std::ptr::addr_of_mut!((*this).extra_pointers) };
|
||||||
|
unsafe { NonNull::new_unchecked(field) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ZST to allow [`super::cancellation_queue`] to utilize the [`Entry::cancel_pointers`]
|
||||||
|
/// by impl [`linked_list::Link`] as we cannot impl it on [`Entry`]
|
||||||
|
/// directly due to the conflicting implementations.
|
||||||
|
///
|
||||||
|
/// This type should never be constructed.
|
||||||
|
pub(super) struct CancellationQueueEntry;
|
||||||
|
|
||||||
|
// Safety: `Entry` is always in an `Arc`.
|
||||||
|
unsafe impl linked_list::Link for CancellationQueueEntry {
|
||||||
|
type Handle = Handle;
|
||||||
|
type Target = Entry;
|
||||||
|
|
||||||
|
fn as_raw(hdl: &Self::Handle) -> NonNull<Self::Target> {
|
||||||
|
unsafe { NonNull::new_unchecked(Arc::as_ptr(&hdl.entry).cast_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self::Handle {
|
||||||
|
Handle {
|
||||||
|
entry: unsafe { Arc::from_raw(ptr.as_ptr()) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn pointers(
|
||||||
|
target: NonNull<Self::Target>,
|
||||||
|
) -> NonNull<linked_list::Pointers<Self::Target>> {
|
||||||
|
let this = target.as_ptr();
|
||||||
|
let field = unsafe { std::ptr::addr_of_mut!((*this).cancel_pointers) };
|
||||||
|
unsafe { NonNull::new_unchecked(field) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ZST to allow [`super::WakeQueue`] to utilize the [`Entry::extra_pointers`]
|
||||||
|
/// by impl [`linked_list::Link`] as we cannot impl it on [`Entry`]
|
||||||
|
/// directly due to the conflicting implementations.
|
||||||
|
///
|
||||||
|
/// This type should never be constructed.
|
||||||
|
pub(super) struct WakeQueueEntry;
|
||||||
|
|
||||||
|
// Safety: `Entry` is always in an `Arc`.
|
||||||
|
unsafe impl linked_list::Link for WakeQueueEntry {
|
||||||
|
type Handle = Handle;
|
||||||
|
type Target = Entry;
|
||||||
|
|
||||||
|
fn as_raw(hdl: &Self::Handle) -> NonNull<Self::Target> {
|
||||||
|
unsafe { NonNull::new_unchecked(Arc::as_ptr(&hdl.entry).cast_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self::Handle {
|
||||||
|
Handle {
|
||||||
|
entry: unsafe { Arc::from_raw(ptr.as_ptr()) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn pointers(
|
||||||
|
target: NonNull<Self::Target>,
|
||||||
|
) -> NonNull<linked_list::Pointers<Self::Target>> {
|
||||||
|
let this = target.as_ptr();
|
||||||
|
let field = unsafe { std::ptr::addr_of_mut!((*this).extra_pointers) };
|
||||||
|
unsafe { NonNull::new_unchecked(field) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Handle {
|
||||||
|
pub(crate) entry: Arc<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Handle> for NonNull<Entry> {
|
||||||
|
fn from(hdl: &Handle) -> Self {
|
||||||
|
// Safety: entry is in an `Arc`, so the pointer is valid.
|
||||||
|
unsafe { NonNull::new_unchecked(Arc::as_ptr(&hdl.entry) as *mut Entry) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
pub(crate) fn new(deadline: u64, waker: Waker) -> Self {
|
||||||
|
let state = State {
|
||||||
|
cancelled: false,
|
||||||
|
woken_up: false,
|
||||||
|
waker: Some(waker),
|
||||||
|
cancel_tx: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry = Arc::new(Entry {
|
||||||
|
cancel_pointers: linked_list::Pointers::new(),
|
||||||
|
extra_pointers: linked_list::Pointers::new(),
|
||||||
|
deadline,
|
||||||
|
state: Mutex::new(state),
|
||||||
|
_pin: PhantomPinned,
|
||||||
|
});
|
||||||
|
|
||||||
|
Handle { entry }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wake the entry if it is already in the pending queue of the timer wheel.
|
||||||
|
pub(crate) fn wake(&self) {
|
||||||
|
let mut lock = self.entry.state.lock();
|
||||||
|
|
||||||
|
if !lock.cancelled {
|
||||||
|
lock.woken_up = true;
|
||||||
|
if let Some(waker) = lock.waker.take() {
|
||||||
|
// unlock before calling waker
|
||||||
|
drop(lock);
|
||||||
|
waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_cancel_tx(&self, cancel_tx: Sender) {
|
||||||
|
let mut lock = self.entry.state.lock();
|
||||||
|
if !lock.cancelled && !lock.woken_up {
|
||||||
|
let old_tx = lock.cancel_tx.replace(cancel_tx);
|
||||||
|
// don't unlock — poisoning the `Mutex` stops others from using the bad state.
|
||||||
|
assert!(old_tx.is_none(), "cancel_tx is already registered");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_waker(&self, waker: Waker) {
|
||||||
|
let mut lock = self.entry.state.lock();
|
||||||
|
if !lock.cancelled && !lock.woken_up {
|
||||||
|
let maybe_old_waker = lock.waker.replace(waker);
|
||||||
|
// unlock before calling waker
|
||||||
|
drop(lock);
|
||||||
|
drop(maybe_old_waker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cancel(&self) {
|
||||||
|
let mut lock = self.entry.state.lock();
|
||||||
|
if !lock.cancelled {
|
||||||
|
lock.cancelled = true;
|
||||||
|
if let Some(cancel_tx) = lock.cancel_tx.take() {
|
||||||
|
drop(lock);
|
||||||
|
|
||||||
|
// Safety: we can guarantee that `self` is not in any cancellation queue
|
||||||
|
// because the `self.cancelled` was just set to `true`.
|
||||||
|
unsafe {
|
||||||
|
cancel_tx.send(self.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deadline(&self) -> u64 {
|
||||||
|
self.entry.deadline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_woken_up(&self) -> bool {
|
||||||
|
let lock = self.entry.state.lock();
|
||||||
|
lock.woken_up
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_cancelled(&self) -> bool {
|
||||||
|
let lock = self.entry.state.lock();
|
||||||
|
lock.cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Only used for unit tests.
|
||||||
|
pub(crate) fn inner_strong_count(&self) -> usize {
|
||||||
|
Arc::strong_count(&self.entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
pub(crate) mod context;
|
||||||
|
pub(super) use context::{LocalContext, TempLocalContext};
|
||||||
|
|
||||||
|
pub(crate) mod cancellation_queue;
|
||||||
|
|
||||||
|
mod entry;
|
||||||
|
pub(crate) use entry::Handle as EntryHandle;
|
||||||
|
use entry::{CancellationQueueEntry, RegistrationQueueEntry, WakeQueueEntry};
|
||||||
|
use entry::{Entry, EntryList};
|
||||||
|
|
||||||
|
mod registration_queue;
|
||||||
|
pub(crate) use registration_queue::RegistrationQueue;
|
||||||
|
|
||||||
|
mod timer;
|
||||||
|
pub(crate) use timer::Timer;
|
||||||
|
|
||||||
|
mod wheel;
|
||||||
|
pub(super) use wheel::Wheel;
|
||||||
|
|
||||||
|
mod wake_queue;
|
||||||
|
pub(crate) use wake_queue::WakeQueue;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use super::{Entry, EntryHandle, RegistrationQueueEntry};
|
||||||
|
use crate::util::linked_list;
|
||||||
|
|
||||||
|
type EntryList = linked_list::LinkedList<RegistrationQueueEntry, Entry>;
|
||||||
|
|
||||||
|
/// A queue of entries that need to be registered in the timer wheel.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct RegistrationQueue {
|
||||||
|
list: EntryList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RegistrationQueue {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// drain all entries without waking them up
|
||||||
|
while let Some(hdl) = self.list.pop_front() {
|
||||||
|
drop(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegistrationQueue {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
list: EntryList::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Behavior is undefined if any of the following conditions are violated:
|
||||||
|
///
|
||||||
|
/// - [`Entry::extra_pointers`] of `hdl` must not being used.
|
||||||
|
pub(crate) unsafe fn push_front(&mut self, hdl: EntryHandle) {
|
||||||
|
self.list.push_front(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop_front(&mut self) -> Option<EntryHandle> {
|
||||||
|
self.list.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use futures::task::noop_waker;
|
||||||
|
|
||||||
|
#[cfg(loom)]
|
||||||
|
const NUM_ITEMS: usize = 16;
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
const NUM_ITEMS: usize = 64;
|
||||||
|
|
||||||
|
fn new_handle() -> EntryHandle {
|
||||||
|
EntryHandle::new(0, noop_waker())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn model<F: Fn() + Send + Sync + 'static>(f: F) {
|
||||||
|
#[cfg(loom)]
|
||||||
|
loom::model(f);
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sanity() {
|
||||||
|
model(|| {
|
||||||
|
let mut queue = RegistrationQueue::new();
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
unsafe {
|
||||||
|
queue.push_front(new_handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
assert!(queue.pop_front().is_some());
|
||||||
|
}
|
||||||
|
assert!(queue.pop_front().is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop_should_not_leak_memory() {
|
||||||
|
model(|| {
|
||||||
|
let mut queue = RegistrationQueue::new();
|
||||||
|
|
||||||
|
let hdls = (0..NUM_ITEMS).map(|_| new_handle()).collect::<Vec<_>>();
|
||||||
|
for hdl in hdls.iter() {
|
||||||
|
unsafe { queue.push_front(hdl.clone()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(queue);
|
||||||
|
|
||||||
|
assert!(hdls.into_iter().all(|hdl| hdl.inner_strong_count() == 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::loom::thread;
|
||||||
|
|
||||||
|
use futures_test::task::{new_count_waker, AwokenCount};
|
||||||
|
|
||||||
|
#[cfg(loom)]
|
||||||
|
const NUM_ITEMS: usize = 16;
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
const NUM_ITEMS: usize = 64;
|
||||||
|
|
||||||
|
fn new_handle() -> (EntryHandle, AwokenCount) {
|
||||||
|
let (waker, count) = new_count_waker();
|
||||||
|
(EntryHandle::new(0, waker), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn model<F: Fn() + Send + Sync + 'static>(f: F) {
|
||||||
|
#[cfg(loom)]
|
||||||
|
loom::model(f);
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wake_up_in_the_same_thread() {
|
||||||
|
model(|| {
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
|
||||||
|
let mut reg_queue = RegistrationQueue::new();
|
||||||
|
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
counts.push(count);
|
||||||
|
unsafe {
|
||||||
|
reg_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wake_queue = WakeQueue::new();
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
if let Some(hdl) = reg_queue.pop_front() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(reg_queue.pop_front().is_none());
|
||||||
|
wake_queue.wake_all();
|
||||||
|
|
||||||
|
assert!(counts.into_iter().all(|c| c.get() == 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cancel_in_the_same_thread() {
|
||||||
|
model(|| {
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
let (cancel_tx, mut cancel_rx) = cancellation_queue::new();
|
||||||
|
|
||||||
|
let mut reg_queue = RegistrationQueue::new();
|
||||||
|
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
hdl.register_cancel_tx(cancel_tx.clone());
|
||||||
|
counts.push(count);
|
||||||
|
unsafe {
|
||||||
|
reg_queue.push_front(hdl.clone());
|
||||||
|
}
|
||||||
|
hdl.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// drain the registration queue
|
||||||
|
while let Some(hdl) = reg_queue.pop_front() {
|
||||||
|
drop(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wake_queue = WakeQueue::new();
|
||||||
|
for hdl in cancel_rx.recv_all() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wake_queue.wake_all();
|
||||||
|
|
||||||
|
assert!(counts.into_iter().all(|c| c.get() == 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wake_up_in_the_different_thread() {
|
||||||
|
model(|| {
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
|
||||||
|
let mut hdls = Vec::new();
|
||||||
|
let mut reg_queue = RegistrationQueue::new();
|
||||||
|
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
counts.push(count);
|
||||||
|
hdls.push(hdl.clone());
|
||||||
|
unsafe {
|
||||||
|
reg_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake up all handles in a different thread
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut wake_queue = WakeQueue::new();
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
if let Some(hdl) = reg_queue.pop_front() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(reg_queue.pop_front().is_none());
|
||||||
|
wake_queue.wake_all();
|
||||||
|
assert!(counts.into_iter().all(|c| c.get() == 1));
|
||||||
|
})
|
||||||
|
.join()
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cancel_in_the_different_thread() {
|
||||||
|
model(|| {
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
let (cancel_tx, mut cancel_rx) = cancellation_queue::new();
|
||||||
|
let mut hdls = Vec::new();
|
||||||
|
let mut reg_queue = RegistrationQueue::new();
|
||||||
|
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
hdl.register_cancel_tx(cancel_tx.clone());
|
||||||
|
counts.push(count);
|
||||||
|
hdls.push(hdl.clone());
|
||||||
|
unsafe {
|
||||||
|
reg_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this thread cancel all handles concurrently
|
||||||
|
let jh = thread::spawn(move || {
|
||||||
|
// cancel all handles
|
||||||
|
for hdl in hdls {
|
||||||
|
hdl.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// cancellation queue concurrently
|
||||||
|
while let Some(hdl) = reg_queue.pop_front() {
|
||||||
|
drop(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wake_queue = WakeQueue::new();
|
||||||
|
for hdl in cancel_rx.recv_all() {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wake_queue.wake_all();
|
||||||
|
assert!(counts.into_iter().all(|c| c.get() == 0));
|
||||||
|
|
||||||
|
jh.join().unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
use super::{EntryHandle, TempLocalContext};
|
||||||
|
use crate::runtime::scheduler::Handle as SchedulerHandle;
|
||||||
|
use crate::time::Instant;
|
||||||
|
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
#[cfg(any(feature = "rt", feature = "rt-multi-thread"))]
|
||||||
|
use crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR;
|
||||||
|
|
||||||
|
pub(crate) struct Timer {
|
||||||
|
sched_handle: SchedulerHandle,
|
||||||
|
|
||||||
|
/// The entry in the timing wheel.
|
||||||
|
///
|
||||||
|
/// - `Some` if the timer is registered / pending / woken up / cancelling.
|
||||||
|
/// - `None` if the timer is unregistered.
|
||||||
|
entry: Option<EntryHandle>,
|
||||||
|
|
||||||
|
/// The deadline for the timer.
|
||||||
|
deadline: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Timer {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Timer")
|
||||||
|
.field("deadline", &self.deadline)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Timer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(entry) = self.entry.take() {
|
||||||
|
entry.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn new(sched_hdl: SchedulerHandle, deadline: Instant) -> Self {
|
||||||
|
// Panic if the time driver is not enabled
|
||||||
|
let _ = sched_hdl.driver().time();
|
||||||
|
Timer {
|
||||||
|
sched_handle: sched_hdl,
|
||||||
|
entry: None,
|
||||||
|
deadline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deadline(&self) -> Instant {
|
||||||
|
self.deadline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_elapsed(&self) -> bool {
|
||||||
|
self.entry.as_ref().is_some_and(|entry| entry.is_woken_up())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
|
||||||
|
with_current_temp_local_context(&this.sched_handle, |maybe_time_cx| {
|
||||||
|
let deadline = deadline_to_tick(&this.sched_handle, this.deadline);
|
||||||
|
|
||||||
|
match maybe_time_cx {
|
||||||
|
Some(TempLocalContext::Running {
|
||||||
|
registration_queue: _,
|
||||||
|
elapsed,
|
||||||
|
}) if deadline <= elapsed => Poll::Ready(()),
|
||||||
|
|
||||||
|
Some(TempLocalContext::Running {
|
||||||
|
registration_queue,
|
||||||
|
elapsed: _,
|
||||||
|
}) => {
|
||||||
|
let hdl = EntryHandle::new(deadline, cx.waker().clone());
|
||||||
|
this.entry = Some(hdl.clone());
|
||||||
|
unsafe {
|
||||||
|
registration_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
Some(TempLocalContext::Shutdown) => panic!("{RUNTIME_SHUTTING_DOWN_ERROR}"),
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
let hdl = EntryHandle::new(deadline, cx.waker().clone());
|
||||||
|
this.entry = Some(hdl.clone());
|
||||||
|
push_from_remote(&this.sched_handle, hdl);
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_elapsed(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
|
match self.entry.as_ref() {
|
||||||
|
Some(entry) if entry.is_woken_up() => Poll::Ready(()),
|
||||||
|
Some(entry) => {
|
||||||
|
entry.register_waker(cx.waker().clone());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
None => self.register(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn scheduler_handle(&self) -> &SchedulerHandle {
|
||||||
|
&self.sched_handle
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
|
pub(crate) fn driver(&self) -> &crate::runtime::time::Handle {
|
||||||
|
self.sched_handle.driver().time()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
|
pub(crate) fn clock(&self) -> &crate::time::Clock {
|
||||||
|
self.sched_handle.driver().clock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_current_temp_local_context<F, R>(hdl: &SchedulerHandle, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<TempLocalContext<'_>>) -> R,
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "rt"))]
|
||||||
|
{
|
||||||
|
let (_, _) = (hdl, f);
|
||||||
|
panic!("Tokio runtime is not enabled, cannot access the current wheel");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rt")]
|
||||||
|
{
|
||||||
|
use crate::runtime::context;
|
||||||
|
|
||||||
|
let is_same_rt =
|
||||||
|
context::with_current(|cur_hdl| cur_hdl.is_same_runtime(hdl)).unwrap_or_default();
|
||||||
|
|
||||||
|
if !is_same_rt {
|
||||||
|
// We don't want to create the timer in one runtime,
|
||||||
|
// but register it in a different runtime's timer wheel.
|
||||||
|
f(None)
|
||||||
|
} else {
|
||||||
|
context::with_scheduler(|maybe_cx| match maybe_cx {
|
||||||
|
Some(cx) => cx.with_time_temp_local_context(f),
|
||||||
|
None => f(None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_from_remote(sched_hdl: &SchedulerHandle, entry_hdl: EntryHandle) {
|
||||||
|
#[cfg(not(feature = "rt"))]
|
||||||
|
{
|
||||||
|
let (_, _) = (sched_hdl, entry_hdl);
|
||||||
|
panic!("Tokio runtime is not enabled, cannot access the current wheel");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rt")]
|
||||||
|
{
|
||||||
|
assert!(!sched_hdl.is_shutdown(), "{RUNTIME_SHUTTING_DOWN_ERROR}");
|
||||||
|
sched_hdl.push_remote_timer(entry_hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deadline_to_tick(sched_hdl: &SchedulerHandle, deadline: Instant) -> u64 {
|
||||||
|
let time_hdl = sched_hdl.driver().time();
|
||||||
|
time_hdl.time_source().deadline_to_tick(deadline)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use super::{Entry, EntryHandle, WakeQueueEntry};
|
||||||
|
use crate::util::linked_list;
|
||||||
|
|
||||||
|
type EntryList = linked_list::LinkedList<WakeQueueEntry, Entry>;
|
||||||
|
|
||||||
|
/// A queue of entries that need to be woken up.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct WakeQueue {
|
||||||
|
list: EntryList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for WakeQueue {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// drain all entries without waking them up
|
||||||
|
while let Some(hdl) = self.list.pop_front() {
|
||||||
|
drop(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WakeQueue {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
list: EntryList::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.list.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Behavior is undefined if any of the following conditions are violated:
|
||||||
|
///
|
||||||
|
/// - [`Entry::extra_pointers`] of `hdl` must not being used.
|
||||||
|
pub(crate) unsafe fn push_front(&mut self, hdl: EntryHandle) {
|
||||||
|
self.list.push_front(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wakes all entries in the wake queue.
|
||||||
|
pub(crate) fn wake_all(mut self) {
|
||||||
|
while let Some(hdl) = self.list.pop_front() {
|
||||||
|
hdl.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use futures_test::task::{new_count_waker, AwokenCount};
|
||||||
|
|
||||||
|
#[cfg(loom)]
|
||||||
|
const NUM_ITEMS: usize = 16;
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
const NUM_ITEMS: usize = 64;
|
||||||
|
|
||||||
|
fn new_handle() -> (EntryHandle, AwokenCount) {
|
||||||
|
let (waker, count) = new_count_waker();
|
||||||
|
(EntryHandle::new(0, waker), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn model<F: Fn() + Send + Sync + 'static>(f: F) {
|
||||||
|
#[cfg(loom)]
|
||||||
|
loom::model(f);
|
||||||
|
|
||||||
|
#[cfg(not(loom))]
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sanity() {
|
||||||
|
model(|| {
|
||||||
|
let mut queue = WakeQueue::new();
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
counts.push(count);
|
||||||
|
unsafe {
|
||||||
|
queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(!queue.is_empty());
|
||||||
|
queue.wake_all();
|
||||||
|
assert!(counts.into_iter().all(|c| c.get() == 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop_should_not_leak_memory() {
|
||||||
|
model(|| {
|
||||||
|
let mut queue = WakeQueue::new();
|
||||||
|
|
||||||
|
let mut hdls = vec![];
|
||||||
|
let mut counts = vec![];
|
||||||
|
for _ in 0..NUM_ITEMS {
|
||||||
|
let (hdl, count) = new_handle();
|
||||||
|
hdls.push(hdl);
|
||||||
|
counts.push(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for hdl in hdls.iter() {
|
||||||
|
unsafe { queue.push_front(hdl.clone()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(queue);
|
||||||
|
|
||||||
|
assert!(hdls.into_iter().all(|hdl| hdl.inner_strong_count() == 1));
|
||||||
|
// drop should not wake any entries
|
||||||
|
assert!(counts.into_iter().all(|count| count.get() == 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
use super::{EntryHandle, EntryList};
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
use std::{array, fmt};
|
||||||
|
|
||||||
|
/// Wheel for a single level in the timer. This wheel contains 64 slots.
|
||||||
|
pub(crate) struct Level {
|
||||||
|
level: usize,
|
||||||
|
|
||||||
|
/// Bit field tracking which slots currently contain entries.
|
||||||
|
///
|
||||||
|
/// Using a bit field to track slots that contain entries allows avoiding a
|
||||||
|
/// scan to find entries. This field is updated when entries are added or
|
||||||
|
/// removed from a slot.
|
||||||
|
///
|
||||||
|
/// The least-significant bit represents slot zero.
|
||||||
|
occupied: u64,
|
||||||
|
|
||||||
|
/// Slots. We access these via the EntryInner `current_list` as well, so this needs to be an `UnsafeCell`.
|
||||||
|
slot: [EntryList; LEVEL_MULT],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates when a slot must be processed next.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Expiration {
|
||||||
|
/// The level containing the slot.
|
||||||
|
pub(crate) level: usize,
|
||||||
|
|
||||||
|
/// The slot index.
|
||||||
|
pub(crate) slot: usize,
|
||||||
|
|
||||||
|
/// The instant at which the slot needs to be processed.
|
||||||
|
pub(crate) deadline: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Level multiplier.
|
||||||
|
///
|
||||||
|
/// Being a power of 2 is very important.
|
||||||
|
const LEVEL_MULT: usize = 64;
|
||||||
|
|
||||||
|
impl Level {
|
||||||
|
pub(crate) fn new(level: usize) -> Level {
|
||||||
|
Level {
|
||||||
|
level,
|
||||||
|
occupied: 0,
|
||||||
|
slot: array::from_fn(|_| EntryList::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the slot that needs to be processed next and returns the slot and
|
||||||
|
/// `Instant` at which this slot must be processed.
|
||||||
|
pub(crate) fn next_expiration(&self, now: u64) -> Option<Expiration> {
|
||||||
|
// Use the `occupied` bit field to get the index of the next slot that
|
||||||
|
// needs to be processed.
|
||||||
|
let slot = self.next_occupied_slot(now)?;
|
||||||
|
|
||||||
|
// From the slot index, calculate the `Instant` at which it needs to be
|
||||||
|
// processed. This value *must* be in the future with respect to `now`.
|
||||||
|
|
||||||
|
let level_range = level_range(self.level);
|
||||||
|
let slot_range = slot_range(self.level);
|
||||||
|
|
||||||
|
// Compute the start date of the current level by masking the low bits
|
||||||
|
// of `now` (`level_range` is a power of 2).
|
||||||
|
let level_start = now & !(level_range - 1);
|
||||||
|
let mut deadline = level_start + slot as u64 * slot_range;
|
||||||
|
|
||||||
|
if deadline <= now {
|
||||||
|
// A timer is in a slot "prior" to the current time. This can occur
|
||||||
|
// because we do not have an infinite hierarchy of timer levels, and
|
||||||
|
// eventually a timer scheduled for a very distant time might end up
|
||||||
|
// being placed in a slot that is beyond the end of all of the
|
||||||
|
// arrays.
|
||||||
|
//
|
||||||
|
// To deal with this, we first limit timers to being scheduled no
|
||||||
|
// more than MAX_DURATION ticks in the future; that is, they're at
|
||||||
|
// most one rotation of the top level away. Then, we force timers
|
||||||
|
// that logically would go into the top+1 level, to instead go into
|
||||||
|
// the top level's slots.
|
||||||
|
//
|
||||||
|
// What this means is that the top level's slots act as a
|
||||||
|
// pseudo-ring buffer, and we rotate around them indefinitely. If we
|
||||||
|
// compute a deadline before now, and it's the top level, it
|
||||||
|
// therefore means we're actually looking at a slot in the future.
|
||||||
|
debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
|
||||||
|
|
||||||
|
deadline += level_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
deadline >= now,
|
||||||
|
"deadline={:016X}; now={:016X}; level={}; lr={:016X}, sr={:016X}, slot={}; occupied={:b}",
|
||||||
|
deadline,
|
||||||
|
now,
|
||||||
|
self.level,
|
||||||
|
level_range,
|
||||||
|
slot_range,
|
||||||
|
slot,
|
||||||
|
self.occupied
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Expiration {
|
||||||
|
level: self.level,
|
||||||
|
slot,
|
||||||
|
deadline,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_occupied_slot(&self, now: u64) -> Option<usize> {
|
||||||
|
if self.occupied == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the slot for now using Maths
|
||||||
|
let now_slot = (now / slot_range(self.level)) as usize;
|
||||||
|
let occupied = self.occupied.rotate_right(now_slot as u32);
|
||||||
|
let zeros = occupied.trailing_zeros() as usize;
|
||||||
|
let slot = (zeros + now_slot) % LEVEL_MULT;
|
||||||
|
|
||||||
|
Some(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn add_entry(&mut self, hdl: EntryHandle) {
|
||||||
|
// Safety: the associated entry must be valid.
|
||||||
|
let deadline = hdl.deadline();
|
||||||
|
let slot = slot_for(deadline, self.level);
|
||||||
|
|
||||||
|
self.slot[slot].push_front(hdl);
|
||||||
|
|
||||||
|
self.occupied |= occupied_bit(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn remove_entry(&mut self, hdl: EntryHandle) {
|
||||||
|
let slot = slot_for(hdl.deadline(), self.level);
|
||||||
|
|
||||||
|
unsafe { self.slot[slot].remove(NonNull::from(&hdl)) };
|
||||||
|
if self.slot[slot].is_empty() {
|
||||||
|
// The bit is currently set
|
||||||
|
debug_assert!(self.occupied & occupied_bit(slot) != 0);
|
||||||
|
|
||||||
|
// Unset the bit
|
||||||
|
self.occupied ^= occupied_bit(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn take_slot(&mut self, slot: usize) -> EntryList {
|
||||||
|
self.occupied &= !occupied_bit(slot);
|
||||||
|
|
||||||
|
std::mem::take(&mut self.slot[slot])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Level {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.debug_struct("Level")
|
||||||
|
.field("occupied", &self.occupied)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn occupied_bit(slot: usize) -> u64 {
|
||||||
|
1 << slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slot_range(level: usize) -> u64 {
|
||||||
|
LEVEL_MULT.pow(level as u32) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level_range(level: usize) -> u64 {
|
||||||
|
LEVEL_MULT as u64 * slot_range(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a duration (milliseconds) and a level to a slot position.
|
||||||
|
fn slot_for(duration: u64, level: usize) -> usize {
|
||||||
|
((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, not(loom)))]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slot_for() {
|
||||||
|
for pos in 0..64 {
|
||||||
|
assert_eq!(pos as usize, slot_for(pos, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for level in 1..5 {
|
||||||
|
for pos in level..64 {
|
||||||
|
let a = pos * 64_usize.pow(level as u32);
|
||||||
|
assert_eq!(pos, slot_for(a as u64, level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
mod level;
|
||||||
|
pub(crate) use self::level::Expiration;
|
||||||
|
use self::level::Level;
|
||||||
|
|
||||||
|
use super::cancellation_queue::Sender;
|
||||||
|
use super::{EntryHandle, EntryList, WakeQueue};
|
||||||
|
|
||||||
|
use std::array;
|
||||||
|
|
||||||
|
/// Timing wheel implementation.
|
||||||
|
///
|
||||||
|
/// This type provides the hashed timing wheel implementation that backs `Timer`
|
||||||
|
/// and `DelayQueue`.
|
||||||
|
///
|
||||||
|
/// The structure is generic over `T: Stack`. This allows handling timeout data
|
||||||
|
/// being stored on the heap or in a slab. In order to support the latter case,
|
||||||
|
/// the slab must be passed into each function allowing the implementation to
|
||||||
|
/// lookup timer entries.
|
||||||
|
///
|
||||||
|
/// See `Timer` documentation for some implementation notes.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Wheel {
|
||||||
|
/// The number of milliseconds elapsed since the wheel started.
|
||||||
|
elapsed: u64,
|
||||||
|
|
||||||
|
/// Timer wheel.
|
||||||
|
///
|
||||||
|
/// Levels:
|
||||||
|
///
|
||||||
|
/// * 1 ms slots / 64 ms range
|
||||||
|
/// * 64 ms slots / ~ 4 sec range
|
||||||
|
/// * ~ 4 sec slots / ~ 4 min range
|
||||||
|
/// * ~ 4 min slots / ~ 4 hr range
|
||||||
|
/// * ~ 4 hr slots / ~ 12 day range
|
||||||
|
/// * ~ 12 day slots / ~ 2 yr range
|
||||||
|
levels: Box<[Level; NUM_LEVELS]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of levels. Each level has 64 slots. By using 6 levels with 64 slots
|
||||||
|
/// each, the timer is able to track time up to 2 years into the future with a
|
||||||
|
/// precision of 1 millisecond.
|
||||||
|
const NUM_LEVELS: usize = 6;
|
||||||
|
|
||||||
|
/// The maximum duration of a `Sleep`.
|
||||||
|
pub(super) const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1;
|
||||||
|
|
||||||
|
impl Wheel {
|
||||||
|
/// Creates a new timing wheel.
|
||||||
|
pub(crate) fn new() -> Wheel {
|
||||||
|
Wheel {
|
||||||
|
elapsed: 0,
|
||||||
|
levels: Box::new(array::from_fn(Level::new)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of milliseconds that have elapsed since the timing
|
||||||
|
/// wheel's creation.
|
||||||
|
pub(crate) fn elapsed(&self) -> u64 {
|
||||||
|
self.elapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts an entry into the timing wheel.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `hdl`: The entry handle to insert into the wheel.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure:
|
||||||
|
///
|
||||||
|
/// * The entry is not already registered in ANY wheel.
|
||||||
|
pub(crate) unsafe fn insert(&mut self, hdl: EntryHandle, cancel_tx: Sender) {
|
||||||
|
let deadline = hdl.deadline();
|
||||||
|
|
||||||
|
assert!(deadline > self.elapsed);
|
||||||
|
|
||||||
|
hdl.register_cancel_tx(cancel_tx);
|
||||||
|
|
||||||
|
// Get the level at which the entry should be stored
|
||||||
|
let level = self.level_for(deadline);
|
||||||
|
unsafe {
|
||||||
|
self.levels[level].add_entry(hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!({
|
||||||
|
self.levels[level]
|
||||||
|
.next_expiration(self.elapsed)
|
||||||
|
.map(|e| e.deadline >= self.elapsed)
|
||||||
|
.unwrap_or(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes `item` from the timing wheel.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure:
|
||||||
|
///
|
||||||
|
/// * The entry is already registered in THIS wheel.
|
||||||
|
pub(crate) unsafe fn remove(&mut self, hdl: EntryHandle) {
|
||||||
|
let deadline = hdl.deadline();
|
||||||
|
debug_assert!(
|
||||||
|
self.elapsed <= deadline,
|
||||||
|
"elapsed={}; deadline={}",
|
||||||
|
self.elapsed,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
|
let level = self.level_for(deadline);
|
||||||
|
unsafe { self.levels[level].remove_entry(hdl.clone()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances the timer up to the instant represented by `now`.
|
||||||
|
pub(crate) fn take_expired(&mut self, now: u64, wake_queue: &mut WakeQueue) {
|
||||||
|
loop {
|
||||||
|
match self.next_expiration() {
|
||||||
|
Some(ref expiration) if expiration.deadline <= now => {
|
||||||
|
self.process_expiration(expiration, wake_queue);
|
||||||
|
|
||||||
|
self.set_elapsed(expiration.deadline);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// in this case the poll did not indicate an expiration
|
||||||
|
// _and_ we were not able to find a next expiration in
|
||||||
|
// the current list of timers. advance to the poll's
|
||||||
|
// current time and do nothing else.
|
||||||
|
self.set_elapsed(now);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the instant at which the next timeout expires.
|
||||||
|
fn next_expiration(&self) -> Option<Expiration> {
|
||||||
|
// Check all levels
|
||||||
|
for (level_num, level) in self.levels.iter().enumerate() {
|
||||||
|
if let Some(expiration) = level.next_expiration(self.elapsed) {
|
||||||
|
// There cannot be any expirations at a higher level that happen
|
||||||
|
// before this one.
|
||||||
|
debug_assert!(self.no_expirations_before(level_num + 1, expiration.deadline));
|
||||||
|
|
||||||
|
return Some(expiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the tick at which this timer wheel next needs to perform some
|
||||||
|
/// processing, or None if there are no timers registered.
|
||||||
|
pub(crate) fn next_expiration_time(&self) -> Option<u64> {
|
||||||
|
self.next_expiration().map(|ex| ex.deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for debug assertions
|
||||||
|
fn no_expirations_before(&self, start_level: usize, before: u64) -> bool {
|
||||||
|
let mut res = true;
|
||||||
|
|
||||||
|
for level in &self.levels[start_level..] {
|
||||||
|
if let Some(e2) = level.next_expiration(self.elapsed) {
|
||||||
|
if e2.deadline < before {
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// iteratively find entries that are between the wheel's current
|
||||||
|
/// time and the expiration time. for each in that population either
|
||||||
|
/// queue it for notification (in the case of the last level) or tier
|
||||||
|
/// it down to the next level (in all other cases).
|
||||||
|
pub(crate) fn process_expiration(
|
||||||
|
&mut self,
|
||||||
|
expiration: &Expiration,
|
||||||
|
wake_queue: &mut WakeQueue,
|
||||||
|
) {
|
||||||
|
// Note that we need to take _all_ of the entries off the list before
|
||||||
|
// processing any of them. This is important because it's possible that
|
||||||
|
// those entries might need to be reinserted into the same slot.
|
||||||
|
//
|
||||||
|
// This happens only on the highest level, when an entry is inserted
|
||||||
|
// more than MAX_DURATION into the future. When this happens, we wrap
|
||||||
|
// around, and process some entries a multiple of MAX_DURATION before
|
||||||
|
// they actually need to be dropped down a level. We then reinsert them
|
||||||
|
// back into the same position; we must make sure we don't then process
|
||||||
|
// those entries again or we'll end up in an infinite loop.
|
||||||
|
let mut entries = self.take_entries(expiration);
|
||||||
|
|
||||||
|
while let Some(hdl) = entries.pop_back() {
|
||||||
|
if expiration.level == 0 {
|
||||||
|
debug_assert_eq!(hdl.deadline(), expiration.deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
let deadline = hdl.deadline();
|
||||||
|
|
||||||
|
if deadline > expiration.deadline {
|
||||||
|
let level = level_for(expiration.deadline, deadline);
|
||||||
|
unsafe {
|
||||||
|
self.levels[level].add_entry(hdl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
wake_queue.push_front(hdl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_elapsed(&mut self, when: u64) {
|
||||||
|
assert!(
|
||||||
|
self.elapsed <= when,
|
||||||
|
"elapsed={:?}; when={:?}",
|
||||||
|
self.elapsed,
|
||||||
|
when
|
||||||
|
);
|
||||||
|
|
||||||
|
if when > self.elapsed {
|
||||||
|
self.elapsed = when;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtains the list of entries that need processing for the given expiration.
|
||||||
|
fn take_entries(&mut self, expiration: &Expiration) -> EntryList {
|
||||||
|
self.levels[expiration.level].take_slot(expiration.slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level_for(&self, when: u64) -> usize {
|
||||||
|
level_for(self.elapsed, when)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level_for(elapsed: u64, when: u64) -> usize {
|
||||||
|
const SLOT_MASK: u64 = (1 << 6) - 1;
|
||||||
|
|
||||||
|
// Mask in the trailing bits ignored by the level calculation in order to cap
|
||||||
|
// the possible leading zeros
|
||||||
|
let mut masked = elapsed ^ when | SLOT_MASK;
|
||||||
|
|
||||||
|
if masked >= MAX_DURATION {
|
||||||
|
// Fudge the timer into the top level
|
||||||
|
masked = MAX_DURATION - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let leading_zeros = masked.leading_zeros() as usize;
|
||||||
|
let significant = 63 - leading_zeros;
|
||||||
|
|
||||||
|
significant / NUM_LEVELS
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, not(loom)))]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_level_for() {
|
||||||
|
for pos in 0..64 {
|
||||||
|
assert_eq!(0, level_for(0, pos), "level_for({pos}) -- binary = {pos:b}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for level in 1..5 {
|
||||||
|
for pos in level..64 {
|
||||||
|
let a = pos * 64_usize.pow(level as u32);
|
||||||
|
assert_eq!(
|
||||||
|
level,
|
||||||
|
level_for(0, a as u64),
|
||||||
|
"level_for({a}) -- binary = {a:b}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if pos > level {
|
||||||
|
let a = a - 1;
|
||||||
|
assert_eq!(
|
||||||
|
level,
|
||||||
|
level_for(0, a as u64),
|
||||||
|
"level_for({a}) -- binary = {a:b}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos < 64 {
|
||||||
|
let a = a + 1;
|
||||||
|
assert_eq!(
|
||||||
|
level,
|
||||||
|
level_for(0, a as u64),
|
||||||
|
"level_for({a}) -- binary = {a:b}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-7
@@ -1,4 +1,4 @@
|
|||||||
use crate::runtime::time::TimerEntry;
|
use crate::runtime::Timer;
|
||||||
use crate::time::{error::Error, Duration, Instant};
|
use crate::time::{error::Error, Duration, Instant};
|
||||||
use crate::util::trace;
|
use crate::util::trace;
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ pin_project! {
|
|||||||
|
|
||||||
// The link between the `Sleep` instance and the timer that drives it.
|
// The link between the `Sleep` instance and the timer that drives it.
|
||||||
#[pin]
|
#[pin]
|
||||||
entry: TimerEntry,
|
entry: Timer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ impl Sleep {
|
|||||||
) -> Sleep {
|
) -> Sleep {
|
||||||
use crate::runtime::scheduler;
|
use crate::runtime::scheduler;
|
||||||
let handle = scheduler::Handle::current();
|
let handle = scheduler::Handle::current();
|
||||||
let entry = TimerEntry::new(handle, deadline);
|
let entry = Timer::new(handle, deadline);
|
||||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
let inner = {
|
let inner = {
|
||||||
let handle = scheduler::Handle::current();
|
let handle = scheduler::Handle::current();
|
||||||
@@ -362,12 +362,30 @@ impl Sleep {
|
|||||||
/// without having it wake up the last task that polled it.
|
/// without having it wake up the last task that polled it.
|
||||||
pub(crate) fn reset_without_reregister(self: Pin<&mut Self>, deadline: Instant) {
|
pub(crate) fn reset_without_reregister(self: Pin<&mut Self>, deadline: Instant) {
|
||||||
let mut me = self.project();
|
let mut me = self.project();
|
||||||
me.entry.as_mut().reset(deadline, false);
|
match me.entry.as_ref().flavor() {
|
||||||
|
crate::runtime::TimerFlavor::Traditional => {
|
||||||
|
me.entry.as_mut().reset(deadline, false);
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
crate::runtime::TimerFlavor::Alternative => {
|
||||||
|
let handle = me.entry.as_ref().scheduler_handle().clone();
|
||||||
|
me.entry.set(Timer::new(handle, deadline));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_inner(self: Pin<&mut Self>, deadline: Instant) {
|
fn reset_inner(self: Pin<&mut Self>, deadline: Instant) {
|
||||||
let mut me = self.project();
|
let mut me = self.project();
|
||||||
me.entry.as_mut().reset(deadline, true);
|
match me.entry.as_ref().flavor() {
|
||||||
|
crate::runtime::TimerFlavor::Traditional => {
|
||||||
|
me.entry.as_mut().reset(deadline, true);
|
||||||
|
}
|
||||||
|
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||||
|
crate::runtime::TimerFlavor::Alternative => {
|
||||||
|
let handle = me.entry.as_ref().scheduler_handle().clone();
|
||||||
|
me.entry.set(Timer::new(handle, deadline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||||
{
|
{
|
||||||
@@ -380,8 +398,8 @@ impl Sleep {
|
|||||||
tracing::trace_span!("runtime.resource.async_op.poll");
|
tracing::trace_span!("runtime.resource.async_op.poll");
|
||||||
|
|
||||||
let duration = {
|
let duration = {
|
||||||
let clock = me.entry.clock();
|
let clock = me.entry.as_ref().clock();
|
||||||
let time_source = me.entry.driver().time_source();
|
let time_source = me.entry.as_ref().driver().time_source();
|
||||||
let now = time_source.now(clock);
|
let now = time_source.now(clock);
|
||||||
let deadline_tick = time_source.deadline_to_tick(deadline);
|
let deadline_tick = time_source.deadline_to_tick(deadline);
|
||||||
deadline_tick.saturating_sub(now)
|
deadline_tick.saturating_sub(now)
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#![warn(rust_2018_idioms)]
|
||||||
|
#![cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
|
||||||
|
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use tokio::time::*;
|
||||||
|
|
||||||
|
fn rt_combinations() -> Vec<Runtime> {
|
||||||
|
let mut rts = vec![];
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
{
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
rts
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sleep() {
|
||||||
|
const N: u32 = 512;
|
||||||
|
|
||||||
|
for rt in rt_combinations() {
|
||||||
|
rt.block_on(async {
|
||||||
|
let mut jhs = vec![];
|
||||||
|
|
||||||
|
// sleep outside of the worker threads
|
||||||
|
let now = Instant::now();
|
||||||
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
assert!(now.elapsed() >= Duration::from_millis(10));
|
||||||
|
|
||||||
|
for _ in 0..N {
|
||||||
|
let jh = tokio::spawn(async move {
|
||||||
|
// sleep inside of the worker threads
|
||||||
|
let now = Instant::now();
|
||||||
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
assert!(now.elapsed() >= Duration::from_millis(10));
|
||||||
|
});
|
||||||
|
jhs.push(jh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for jh in jhs {
|
||||||
|
jh.await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout() {
|
||||||
|
const N: u32 = 512;
|
||||||
|
|
||||||
|
for rt in rt_combinations() {
|
||||||
|
rt.block_on(async {
|
||||||
|
let mut jhs = vec![];
|
||||||
|
|
||||||
|
// timeout outside of the worker threads
|
||||||
|
let now = Instant::now();
|
||||||
|
tokio::time::timeout(Duration::from_millis(10), std::future::pending::<()>())
|
||||||
|
.await
|
||||||
|
.expect_err("timeout should occur");
|
||||||
|
assert!(now.elapsed() >= Duration::from_millis(10));
|
||||||
|
|
||||||
|
for _ in 0..N {
|
||||||
|
let jh = tokio::spawn(async move {
|
||||||
|
let now = Instant::now();
|
||||||
|
// timeout inside of the worker threads
|
||||||
|
tokio::time::timeout(Duration::from_millis(10), std::future::pending::<()>())
|
||||||
|
.await
|
||||||
|
.expect_err("timeout should occur");
|
||||||
|
assert!(now.elapsed() >= Duration::from_millis(10));
|
||||||
|
});
|
||||||
|
jhs.push(jh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for jh in jhs {
|
||||||
|
jh.await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,19 +13,64 @@ mod support {
|
|||||||
}
|
}
|
||||||
use support::panic::test_panic;
|
use support::panic::test_panic;
|
||||||
|
|
||||||
|
fn rt_combinations() -> Vec<Runtime> {
|
||||||
|
let mut rts = vec![];
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
{
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
rts
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pause_panic_caller() -> Result<(), Box<dyn Error>> {
|
fn pause_panic_caller() -> Result<(), Box<dyn Error>> {
|
||||||
let panic_location_file = test_panic(|| {
|
for rt in rt_combinations() {
|
||||||
let rt = current_thread();
|
let panic_location_file = test_panic(|| {
|
||||||
|
rt.block_on(async {
|
||||||
rt.block_on(async {
|
time::pause();
|
||||||
time::pause();
|
time::pause();
|
||||||
time::pause();
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// The panic location should be in this file
|
// The panic location should be in this file
|
||||||
assert_eq!(&panic_location_file.unwrap(), file!());
|
assert_eq!(&panic_location_file.unwrap(), file!());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-23
@@ -1,28 +1,96 @@
|
|||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
#![cfg(feature = "full")]
|
#![cfg(feature = "full")]
|
||||||
|
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
use tokio::time::*;
|
use tokio::time::*;
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
fn rt_combinations() -> Vec<Runtime> {
|
||||||
|
let mut rts = vec![];
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
{
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.enable_alt_timer()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rts.push(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
rts
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "rt-multi-thread", not(target_os = "wasi")))] // Wasi doesn't support threads
|
#[cfg(all(feature = "rt-multi-thread", not(target_os = "wasi")))] // Wasi doesn't support threads
|
||||||
#[test]
|
#[test]
|
||||||
fn timer_with_threaded_runtime() {
|
fn timer_with_threaded_runtime() {
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
let rt = Runtime::new().unwrap();
|
{
|
||||||
let (tx, rx) = mpsc::channel();
|
let rt = Runtime::new().unwrap();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
let when = Instant::now() + Duration::from_millis(10);
|
let when = Instant::now() + Duration::from_millis(10);
|
||||||
|
|
||||||
sleep_until(when).await;
|
sleep_until(when).await;
|
||||||
assert!(Instant::now() >= when);
|
assert!(Instant::now() >= when);
|
||||||
|
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
rx.recv().unwrap();
|
rx.recv().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
{
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_alt_timer()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
rt.block_on(async move {
|
||||||
|
let when = Instant::now() + Duration::from_millis(10);
|
||||||
|
|
||||||
|
sleep_until(when).await;
|
||||||
|
assert!(Instant::now() >= when);
|
||||||
|
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
rx.recv().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -44,8 +112,8 @@ fn timer_with_current_thread_scheduler() {
|
|||||||
rx.recv().unwrap();
|
rx.recv().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn starving() {
|
fn starving() {
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
@@ -68,23 +136,31 @@ async fn starving() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let when = Instant::now() + Duration::from_millis(10);
|
for rt in rt_combinations() {
|
||||||
let starve = Starve(Box::pin(sleep_until(when)), 0);
|
rt.block_on(async {
|
||||||
|
let when = Instant::now() + Duration::from_millis(10);
|
||||||
|
let starve = Starve(Box::pin(sleep_until(when)), 0);
|
||||||
|
|
||||||
starve.await;
|
starve.await;
|
||||||
assert!(Instant::now() >= when);
|
assert!(Instant::now() >= when);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn timeout_value() {
|
fn timeout_value() {
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
let (_tx, rx) = oneshot::channel::<()>();
|
for rt in rt_combinations() {
|
||||||
|
rt.block_on(async {
|
||||||
|
let (_tx, rx) = oneshot::channel::<()>();
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let dur = Duration::from_millis(10);
|
let dur = Duration::from_millis(10);
|
||||||
|
|
||||||
let res = timeout(dur, rx).await;
|
let res = timeout(dur, rx).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
assert!(Instant::now() >= now + dur);
|
assert!(Instant::now() >= now + dur);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user