mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
time: enforce current_thread rt for time::pause (#3289)
Pausing time is a capability added to assist with testing Tokio code dependent on time. Currently, the capability implicitly requires the current_thread runtime. This change enforces the requirement by panicking if called from a multi-threaded runtime.
This commit is contained in:
@@ -379,6 +379,11 @@ impl Builder {
|
|||||||
|
|
||||||
fn get_cfg(&self) -> driver::Cfg {
|
fn get_cfg(&self) -> driver::Cfg {
|
||||||
driver::Cfg {
|
driver::Cfg {
|
||||||
|
enable_pause_time: match self.kind {
|
||||||
|
Kind::CurrentThread => true,
|
||||||
|
#[cfg(feature = "rt-multi-thread")]
|
||||||
|
Kind::MultiThread => false,
|
||||||
|
},
|
||||||
enable_io: self.enable_io,
|
enable_io: self.enable_io,
|
||||||
enable_time: self.enable_time,
|
enable_time: self.enable_time,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ cfg_time! {
|
|||||||
pub(crate) type Clock = crate::time::Clock;
|
pub(crate) type Clock = crate::time::Clock;
|
||||||
pub(crate) type TimeHandle = Option<crate::time::driver::Handle>;
|
pub(crate) type TimeHandle = Option<crate::time::driver::Handle>;
|
||||||
|
|
||||||
fn create_clock() -> Clock {
|
fn create_clock(enable_pausing: bool) -> Clock {
|
||||||
crate::time::Clock::new()
|
crate::time::Clock::new(enable_pausing)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_time_driver(
|
fn create_time_driver(
|
||||||
@@ -131,7 +131,7 @@ cfg_not_time! {
|
|||||||
pub(crate) type Clock = ();
|
pub(crate) type Clock = ();
|
||||||
pub(crate) type TimeHandle = ();
|
pub(crate) type TimeHandle = ();
|
||||||
|
|
||||||
fn create_clock() -> Clock {
|
fn create_clock(_enable_pausing: bool) -> Clock {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +161,14 @@ pub(crate) struct Resources {
|
|||||||
pub(crate) struct Cfg {
|
pub(crate) struct Cfg {
|
||||||
pub(crate) enable_io: bool,
|
pub(crate) enable_io: bool,
|
||||||
pub(crate) enable_time: bool,
|
pub(crate) enable_time: bool,
|
||||||
|
pub(crate) enable_pause_time: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Driver {
|
impl Driver {
|
||||||
pub(crate) fn new(cfg: Cfg) -> io::Result<(Self, Resources)> {
|
pub(crate) fn new(cfg: Cfg) -> io::Result<(Self, Resources)> {
|
||||||
let (io_stack, io_handle, signal_handle) = create_io_stack(cfg.enable_io)?;
|
let (io_stack, io_handle, signal_handle) = create_io_stack(cfg.enable_io)?;
|
||||||
|
|
||||||
let clock = create_clock();
|
let clock = create_clock(cfg.enable_pause_time);
|
||||||
let (time_driver, time_handle) =
|
let (time_driver, time_handle) =
|
||||||
create_time_driver(cfg.enable_time, io_stack, clock.clone());
|
create_time_driver(cfg.enable_time, io_stack, clock.clone());
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -17,7 +17,7 @@ cfg_not_test_util! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
pub(crate) fn new() -> Clock {
|
pub(crate) fn new(_enable_pausing: bool) -> Clock {
|
||||||
Clock {}
|
Clock {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +59,9 @@ cfg_test_util! {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
|
/// True if the ability to pause time is enabled.
|
||||||
|
enable_pausing: bool,
|
||||||
|
|
||||||
/// Instant to use as the clock's base instant.
|
/// Instant to use as the clock's base instant.
|
||||||
base: std::time::Instant,
|
base: std::time::Instant,
|
||||||
|
|
||||||
@@ -69,14 +72,18 @@ cfg_test_util! {
|
|||||||
/// Pause time
|
/// Pause time
|
||||||
///
|
///
|
||||||
/// The current value of `Instant::now()` is saved and all subsequent calls
|
/// The current value of `Instant::now()` is saved and all subsequent calls
|
||||||
/// to `Instant::now()` until the timer wheel is checked again will return the saved value.
|
/// to `Instant::now()` until the timer wheel is checked again will return
|
||||||
/// Once the timer wheel is checked, time will immediately advance to the next registered
|
/// the saved value. Once the timer wheel is checked, time will immediately
|
||||||
/// `Sleep`. This is useful for running tests that depend on time.
|
/// advance to the next registered `Sleep`. This is useful for running tests
|
||||||
|
/// that depend on time.
|
||||||
|
///
|
||||||
|
/// Pausing time requires the `current_thread` Tokio runtime. This is the
|
||||||
|
/// default runtime used by `#[tokio::test]`
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if time is already frozen or if called from outside of the Tokio
|
/// Panics if time is already frozen or if called from outside of a
|
||||||
/// runtime.
|
/// `current_thread` Tokio runtime.
|
||||||
pub fn pause() {
|
pub fn pause() {
|
||||||
let clock = clock().expect("time cannot be frozen from outside the Tokio runtime");
|
let clock = clock().expect("time cannot be frozen from outside the Tokio runtime");
|
||||||
clock.pause();
|
clock.pause();
|
||||||
@@ -142,11 +149,12 @@ cfg_test_util! {
|
|||||||
impl Clock {
|
impl Clock {
|
||||||
/// Return a new `Clock` instance that uses the current execution context's
|
/// Return a new `Clock` instance that uses the current execution context's
|
||||||
/// source of time.
|
/// source of time.
|
||||||
pub(crate) fn new() -> Clock {
|
pub(crate) fn new(enable_pausing: bool) -> Clock {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
Clock {
|
Clock {
|
||||||
inner: Arc::new(Mutex::new(Inner {
|
inner: Arc::new(Mutex::new(Inner {
|
||||||
|
enable_pausing,
|
||||||
base: now,
|
base: now,
|
||||||
unfrozen: Some(now),
|
unfrozen: Some(now),
|
||||||
})),
|
})),
|
||||||
@@ -156,6 +164,12 @@ cfg_test_util! {
|
|||||||
pub(crate) fn pause(&self) {
|
pub(crate) fn pause(&self) {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
if !inner.enable_pausing {
|
||||||
|
drop(inner); // avoid poisoning the lock
|
||||||
|
panic!("`time::pause()` requires the `current_thread` Tokio runtime. \
|
||||||
|
This is the default Runtime used by `#[tokio::test].");
|
||||||
|
}
|
||||||
|
|
||||||
let elapsed = inner.unfrozen.as_ref().expect("time is already frozen").elapsed();
|
let elapsed = inner.unfrozen.as_ref().expect("time is already frozen").elapsed();
|
||||||
inner.base += elapsed;
|
inner.base += elapsed;
|
||||||
inner.unfrozen = None;
|
inner.unfrozen = None;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fn model(f: impl Fn() + Send + Sync + 'static) {
|
|||||||
#[test]
|
#[test]
|
||||||
fn single_timer() {
|
fn single_timer() {
|
||||||
model(|| {
|
model(|| {
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
|
|
||||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||||
@@ -72,7 +72,7 @@ fn single_timer() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn drop_timer() {
|
fn drop_timer() {
|
||||||
model(|| {
|
model(|| {
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
|
|
||||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||||
@@ -103,7 +103,7 @@ fn drop_timer() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn change_waker() {
|
fn change_waker() {
|
||||||
model(|| {
|
model(|| {
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
|
|
||||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||||
@@ -138,7 +138,7 @@ fn reset_future() {
|
|||||||
model(|| {
|
model(|| {
|
||||||
let finished_early = Arc::new(AtomicBool::new(false));
|
let finished_early = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
|
|
||||||
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
let inner = super::Inner::new(time_source.clone(), MockUnpark::mock());
|
||||||
@@ -185,7 +185,7 @@ fn reset_future() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(loom))]
|
#[cfg(not(loom))]
|
||||||
fn poll_process_levels() {
|
fn poll_process_levels() {
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
clock.pause();
|
clock.pause();
|
||||||
|
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
@@ -226,7 +226,7 @@ fn poll_process_levels() {
|
|||||||
fn poll_process_levels_targeted() {
|
fn poll_process_levels_targeted() {
|
||||||
let mut context = Context::from_waker(noop_waker_ref());
|
let mut context = Context::from_waker(noop_waker_ref());
|
||||||
|
|
||||||
let clock = crate::time::clock::Clock::new();
|
let clock = crate::time::clock::Clock::new(true);
|
||||||
clock.pause();
|
clock.pause();
|
||||||
|
|
||||||
let time_source = super::ClockTime::new(clock.clone());
|
let time_source = super::ClockTime::new(clock.clone());
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#![warn(rust_2018_idioms)]
|
||||||
|
#![cfg(feature = "full")]
|
||||||
|
|
||||||
|
use tokio_test::assert_err;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pause_time_in_main() {
|
||||||
|
tokio::time::pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pause_time_in_task() {
|
||||||
|
let t = tokio::spawn(async {
|
||||||
|
tokio::time::pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
|
#[should_panic]
|
||||||
|
async fn pause_time_in_main_threads() {
|
||||||
|
tokio::time::pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||||
|
async fn pause_time_in_spawn_threads() {
|
||||||
|
let t = tokio::spawn(async {
|
||||||
|
tokio::time::pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_err!(t.await);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user