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:
Carl Lerche
2020-12-17 15:37:08 -08:00
committed by GitHub
parent c5861ef62f
commit abd4c00255
5 changed files with 70 additions and 17 deletions
+5
View File
@@ -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,
} }
+5 -4
View File
@@ -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
View File
@@ -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;
+6 -6
View File
@@ -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());
+33
View File
@@ -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);
}