use std::{future::poll_fn, pin::Pin, task::Poll, time::Duration};
#[cfg(feature = "tokio")]
mod tokio;
#[cfg(feature = "tokio")]
pub type TokioClock = tokio::Clock;
#[cfg(feature = "async-io")]
mod async_io;
#[cfg(feature = "async-io")]
pub type AsyncIoClock = async_io::Clock;
pub trait InstantExt:
Copy
+ Ord
+ std::ops::Add<Duration, Output = Self>
+ std::ops::Sub<Duration, Output = Self>
+ Unpin
+ From<std::time::Instant>
+ Into<std::time::Instant>
{
fn saturating_duration_since(&self, earlier: Self) -> Duration;
}
pub trait Clock: Clone + Unpin + Sized + Default {
type Instant: InstantExt;
type SleepFuture: std::future::Future<Output = ()> + Send + 'static;
fn now(&self) -> Self::Instant;
fn sleep(&self, duration: Duration) -> Self::SleepFuture;
#[cfg(test)]
fn elapsed(&self, earlier: Self::Instant) -> Duration {
self.now().saturating_duration_since(earlier)
}
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
mod default {
pub type Instant = <Clock as super::Clock>::Instant;
#[derive(Debug, Default, Clone)]
pub struct Clock {
#[cfg(feature = "tokio")]
inner: crate::tokio::Clock,
#[cfg(feature = "async-io")]
inner: crate::async_io::Clock,
}
impl super::Clock for Clock {
#[cfg(feature = "tokio")]
type Instant = ::tokio::time::Instant;
#[cfg(feature = "tokio")]
type SleepFuture = ::tokio::time::Sleep;
#[cfg(feature = "async-io")]
type Instant = std::time::Instant;
#[cfg(feature = "async-io")]
type SleepFuture = crate::async_io::WrappedTimer;
fn now(&self) -> Self::Instant {
self.inner.now()
}
fn sleep(&self, duration: std::time::Duration) -> Self::SleepFuture {
self.inner.sleep(duration)
}
}
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub use default::Clock as DefaultClock;
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub use default::Instant;
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub fn sleep(duration: Duration) -> Sleep<DefaultClock> {
sleep_with_clock(DefaultClock::default(), duration)
}
pub fn sleep_with_clock<C: Clock>(clock: C, duration: Duration) -> Sleep<C> {
let deadline = clock.now() + duration;
Sleep::new(clock, deadline)
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub fn sleep_until(deadline: std::time::Instant) -> Sleep<DefaultClock> {
#[allow(clippy::useless_conversion)]
sleep_until_with_clock(DefaultClock::default(), deadline.into())
}
pub fn sleep_until_with_clock<C: Clock>(clock: C, deadline: C::Instant) -> Sleep<C> {
Sleep::new(clock, deadline)
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub fn interval(period: Duration) -> Interval<DefaultClock> {
interval_with_clock(DefaultClock::default(), period)
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub fn sleep_builder(deadline: std::time::Instant) -> SleepBuilder {
SleepBuilder::new(deadline)
}
pub fn interval_with_clock<C: Clock>(clock: C, period: Duration) -> Interval<C> {
Interval::new(clock, period)
}
#[derive(Debug)]
pub struct Sleep<C: Clock> {
clock: C,
deadline: C::Instant,
threshold: Duration,
inner_sleep: Option<Pin<Box<C::SleepFuture>>>,
#[cfg(all(feature = "test-util", feature = "tokio"))]
start_paused: bool,
#[cfg(all(feature = "test-util", feature = "tokio"))]
test_util_inner_sleep: Option<Pin<Box<::tokio::time::Sleep>>>,
}
impl<C: Clock> Sleep<C> {
pub const DEFAULT_SLEEP_THRESHOLD: Duration = Duration::from_millis(10);
pub fn new(clock: C, deadline: C::Instant) -> Self {
Self {
clock,
deadline,
threshold: Self::DEFAULT_SLEEP_THRESHOLD,
inner_sleep: None,
#[cfg(all(feature = "test-util", feature = "tokio"))]
start_paused: false,
#[cfg(all(feature = "test-util", feature = "tokio"))]
test_util_inner_sleep: None,
}
}
pub fn reset(self: Pin<&mut Self>, new_deadline: C::Instant) {
let this = self.get_mut();
this.deadline = new_deadline;
this.inner_sleep = None; #[cfg(all(feature = "test-util", feature = "tokio"))]
{
this.test_util_inner_sleep = None;
}
}
fn clock(&self) -> &C {
&self.clock
}
}
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
pub struct SleepBuilder {
inner: inner::SleepBuilder<DefaultClock>,
}
#[cfg(not(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
)))]
pub struct SleepBuilder<C: Clock> {
inner: inner::SleepBuilder<C>,
}
mod inner {
use std::time::Duration;
pub(crate) struct SleepBuilder<C: super::Clock> {
pub(crate) deadline: std::time::Instant,
pub(crate) threshold: Option<Duration>,
pub(crate) clock: Option<C>,
#[cfg(all(feature = "test-util", feature = "tokio"))]
pub(crate) start_paused: bool,
}
}
macro_rules! builder_methods {
($C:ty) => {
pub fn new(deadline: std::time::Instant) -> Self {
Self {
inner: inner::SleepBuilder {
deadline,
threshold: None,
clock: None,
#[cfg(all(feature = "test-util", feature = "tokio"))]
start_paused: false,
},
}
}
pub fn threshold(&mut self, threshold: Duration) -> &mut Self {
self.inner.threshold = Some(threshold);
self
}
#[cfg(all(feature = "test-util", feature = "tokio"))]
pub fn start_paused(&mut self, start_paused: bool) -> &mut Self {
self.inner.start_paused = start_paused;
self
}
pub fn build(&self) -> Sleep<$C> {
let clock: $C = self.inner.clock.clone().unwrap_or_default();
Sleep {
deadline: self.inner.deadline.into(),
threshold: self
.inner
.threshold
.unwrap_or(Sleep::<$C>::DEFAULT_SLEEP_THRESHOLD),
clock,
inner_sleep: None,
#[cfg(all(feature = "test-util", feature = "tokio"))]
start_paused: self.inner.start_paused,
#[cfg(all(feature = "test-util", feature = "tokio"))]
test_util_inner_sleep: None,
}
}
};
}
macro_rules! builder_impl {
() => {
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
impl SleepBuilder {
builder_methods!(DefaultClock);
}
#[cfg(not(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
)))]
impl<C: Clock> SleepBuilder<C> {
builder_methods!(C);
}
};
}
builder_impl!();
impl<C: Clock> Future for Sleep<C> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
let remaining = self.deadline.saturating_duration_since(self.clock.now());
if remaining.is_zero() {
return Poll::Ready(());
}
if remaining > self.threshold {
if self.inner_sleep.is_none() {
self.inner_sleep = Some(Box::pin(self.clock.sleep(remaining - self.threshold)));
}
match self.inner_sleep.as_mut().unwrap().as_mut().poll(cx) {
Poll::Ready(_) => {
self.inner_sleep = None;
return Poll::Pending; }
Poll::Pending => return Poll::Pending,
}
}
#[cfg(all(feature = "test-util", feature = "tokio"))]
{
if self.start_paused {
if self.test_util_inner_sleep.is_none() {
self.test_util_inner_sleep = Some(Box::pin(::tokio::time::sleep(remaining)));
}
return match self
.test_util_inner_sleep
.as_mut()
.unwrap()
.as_mut()
.poll(cx)
{
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => Poll::Pending,
};
}
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
pub struct Interval<C: Clock> {
delay: Pin<Box<Sleep<C>>>,
period: Duration,
}
impl<C: Clock> Interval<C> {
pub fn new(clock: C, period: Duration) -> Self {
let deadline = clock.now() + period;
Self {
delay: Box::pin(Sleep::new(clock, deadline)),
period,
}
}
pub fn reset(&mut self) {
self.reset_inner(self.delay.clock().now() + self.period);
}
pub fn reset_immediately(&mut self) {
self.reset_inner(self.delay.clock().now());
}
fn reset_inner(&mut self, deadline: C::Instant) {
self.delay.as_mut().reset(deadline);
}
pub async fn tick(&mut self) {
poll_fn(|cx| self.poll_tick(cx)).await
}
pub fn poll_tick(&mut self, cx: &mut std::task::Context<'_>) -> Poll<()> {
match self.delay.as_mut().poll(cx) {
Poll::Ready(_) => {
self.reset(); Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
pub(crate) async fn test_sleep<C: Clock>(clock: C) {
let start = clock.now();
super::sleep_with_clock(clock.clone(), Duration::from_millis(50)).await;
assert!(clock.elapsed(start) >= Duration::from_millis(50));
}
pub(crate) async fn test_sleep_until<C: Clock>(clock: C) {
let start = clock.now();
let deadline = start + Duration::from_millis(50);
super::sleep_until_with_clock(clock.clone(), deadline).await;
assert!(clock.elapsed(start) >= Duration::from_millis(50));
}
pub(crate) async fn test_sleep_already_passed<C: Clock>(clock: C) {
let start = clock.now();
let deadline = start - Duration::from_millis(10);
super::sleep_until_with_clock(clock.clone(), deadline).await;
assert!(clock.elapsed(start) < Duration::from_millis(50));
}
pub(crate) async fn test_sleep_short_duration<C: Clock>(clock: C) {
let start = clock.now();
let deadline = start + Duration::from_millis(5);
super::sleep_until_with_clock(clock.clone(), deadline).await;
assert!(clock.elapsed(start) >= Duration::from_millis(5));
}
pub(crate) async fn test_sleep_long_duration<C: Clock>(clock: C) {
let start = clock.now();
let deadline = start + Duration::from_millis(100);
super::sleep_until_with_clock(clock.clone(), deadline).await;
assert!(clock.elapsed(start) >= Duration::from_millis(100));
}
pub(crate) async fn test_interval_basic<C: Clock>(clock: C) {
let period = Duration::from_millis(50);
let start = clock.now();
let mut interval = super::interval_with_clock(clock.clone(), period);
interval.tick().await;
assert!(clock.elapsed(start) >= period);
interval.tick().await;
assert!(clock.elapsed(start) >= period * 2);
}
pub(crate) async fn test_interval_reset<C: Clock>(clock: C) {
let period = Duration::from_millis(50);
let mut interval = super::interval_with_clock(clock.clone(), period);
interval.tick().await;
let before_reset = clock.now();
interval.reset();
interval.tick().await;
assert!(clock.elapsed(before_reset) >= period);
}
pub(crate) async fn test_interval_reset_immediately<C: Clock>(clock: C) {
let period = Duration::from_millis(100);
let mut interval = super::interval_with_clock(clock.clone(), period);
interval.tick().await;
let before_reset = clock.now();
interval.reset_immediately();
interval.tick().await;
assert!(clock.elapsed(before_reset) < period);
}
pub(crate) async fn test_interval_multiple_ticks<C: Clock>(clock: C) {
let period = Duration::from_millis(30);
let start = clock.now();
let mut interval = super::interval_with_clock(clock.clone(), period);
for i in 1..=4u32 {
interval.tick().await;
assert!(clock.elapsed(start) >= period * i);
}
}
}
#[cfg(all(test, feature = "tokio"))]
mod tokio_tests {
use super::tokio::Clock as TokioClock;
use super::{Duration, SleepBuilder};
#[::tokio::test]
async fn test_sleep() {
super::tests::test_sleep(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_sleep_until() {
super::tests::test_sleep_until(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_sleep_already_passed() {
super::tests::test_sleep_already_passed(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_sleep_short_duration() {
super::tests::test_sleep_short_duration(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_sleep_long_duration() {
super::tests::test_sleep_long_duration(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_interval_basic() {
super::tests::test_interval_basic(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_interval_reset() {
super::tests::test_interval_reset(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_interval_reset_immediately() {
super::tests::test_interval_reset_immediately(TokioClock::new()).await;
}
#[::tokio::test]
async fn test_interval_multiple_ticks() {
super::tests::test_interval_multiple_ticks(TokioClock::new()).await;
}
#[::tokio::test(start_paused = true)]
#[cfg(feature = "test-util")]
async fn test_sleep_with_time_advance() {
use ::tokio::time::Instant;
let start = Instant::now();
let deadline = start + Duration::from_millis(50);
let sleep_future = SleepBuilder::new(deadline.into())
.start_paused(true)
.build();
::tokio::time::advance(Duration::from_millis(30)).await;
assert!(start.elapsed() >= Duration::from_millis(30));
sleep_future.await;
assert!(start.elapsed() >= Duration::from_millis(50));
}
#[::tokio::test]
async fn test_sleep_move_to_task() {
use ::tokio::time::Instant;
let start = Instant::now();
let deadline = start + Duration::from_millis(50);
#[cfg(all(feature = "tokio", not(feature = "async-io")))]
let sleep_future = super::sleep_until(deadline.into());
#[cfg(all(feature = "tokio", feature = "async-io"))]
let sleep_future = super::sleep_until_with_clock(super::TokioClock::new(), deadline);
::tokio::spawn(async move {
sleep_future.await;
})
.await
.unwrap();
assert!(start.elapsed() >= Duration::from_millis(50));
}
#[::tokio::test]
async fn test_sleep_reset() {
use ::tokio::time::Instant;
let start = Instant::now();
let deadline = start + Duration::from_millis(50);
#[cfg(all(feature = "tokio", not(feature = "async-io")))]
let mut sleep_future = super::sleep_until(deadline.into());
#[cfg(all(feature = "tokio", feature = "async-io"))]
let mut sleep_future = super::sleep_until_with_clock(super::TokioClock::new(), deadline);
let mut _completed = false;
let mut reset_done = false;
loop {
::tokio::select! {
_ = &mut sleep_future => {
assert!(start.elapsed() >= Duration::from_millis(100));
_completed = true;
break;
}
_ = ::tokio::time::sleep(Duration::from_millis(30)), if !reset_done => {
let new_deadline = Instant::now() + Duration::from_millis(70);
Pin::new(&mut sleep_future).reset(new_deadline);
reset_done = true;
}
}
}
assert!(_completed);
#[cfg(all(feature = "tokio", not(feature = "async-io")))]
let mut sleep_future = SleepBuilder::new((start + Duration::from_millis(50)).into())
.threshold(Duration::from_millis(30))
.build();
#[cfg(all(feature = "tokio", feature = "async-io"))]
let mut sleep_future =
SleepBuilder::<TokioClock>::new((start + Duration::from_millis(50)).into())
.threshold(Duration::from_millis(30))
.build();
let mut _completed = false;
let mut reset_done = false;
loop {
::tokio::select! {
_ = &mut sleep_future => {
assert!(start.elapsed() >= Duration::from_millis(100));
_completed = true;
break;
}
_ = ::tokio::time::sleep(Duration::from_millis(30)), if !reset_done => {
let new_deadline = Instant::now() + Duration::from_millis(70);
Pin::new(&mut sleep_future).reset(new_deadline);
reset_done = true;
}
}
}
assert!(_completed);
}
}
#[cfg(all(test, feature = "smol"))]
mod smol_tests {
smol_macros::test! {
async fn test_sleep(_ex: &smol::Executor<'_>) {
super::tests::test_sleep(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_sleep_until(_ex: &smol::Executor<'_>) {
super::tests::test_sleep_until(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_sleep_already_passed(_ex: &smol::Executor<'_>) {
super::tests::test_sleep_already_passed(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_sleep_short_duration(_ex: &smol::Executor<'_>) {
super::tests::test_sleep_short_duration(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_sleep_long_duration(_ex: &smol::Executor<'_>) {
super::tests::test_sleep_long_duration(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_interval_basic(_ex: &smol::Executor<'_>) {
super::tests::test_interval_basic(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_interval_reset(_ex: &smol::Executor<'_>) {
super::tests::test_interval_reset(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_interval_reset_immediately(_ex: &smol::Executor<'_>) {
super::tests::test_interval_reset_immediately(crate::async_io::Clock).await;
}
}
smol_macros::test! {
async fn test_interval_multiple_ticks(_ex: &smol::Executor<'_>) {
super::tests::test_interval_multiple_ticks(crate::async_io::Clock).await;
}
}
}
#[cfg(test)]
mod mock_tests {
use super::*;
use std::ops::{Add, Sub};
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct MockInstant {
now: std::time::Instant,
}
impl MockInstant {
fn now() -> Self {
Self {
now: std::time::Instant::now(),
}
}
}
impl Default for MockInstant {
fn default() -> Self {
Self::now()
}
}
impl Add<std::time::Duration> for MockInstant {
type Output = Self;
fn add(self, rhs: std::time::Duration) -> Self {
Self {
now: self.now + rhs,
}
}
}
impl Sub<std::time::Duration> for MockInstant {
type Output = Self;
fn sub(self, rhs: std::time::Duration) -> Self {
Self {
now: self.now - rhs,
}
}
}
impl From<std::time::Instant> for MockInstant {
fn from(now: std::time::Instant) -> Self {
Self { now }
}
}
impl From<MockInstant> for std::time::Instant {
fn from(mock: MockInstant) -> Self {
mock.now
}
}
impl InstantExt for MockInstant {
fn saturating_duration_since(&self, earlier: Self) -> Duration {
self.now.saturating_duration_since(earlier.now)
}
}
struct MockSleepFuture {
duration: Duration,
deadline: Option<std::time::Instant>,
}
impl std::future::Future for MockSleepFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let dur = self.duration;
let deadline = *self
.deadline
.get_or_insert_with(|| std::time::Instant::now() + dur);
if std::time::Instant::now() >= deadline {
return Poll::Ready(());
}
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if remaining > Duration::from_millis(1) {
std::thread::sleep(remaining - Duration::from_millis(1));
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
#[derive(Debug, Clone, Default)]
struct MockClock;
impl Clock for MockClock {
type Instant = MockInstant;
type SleepFuture = MockSleepFuture;
fn now(&self) -> MockInstant {
MockInstant::now()
}
fn sleep(&self, duration: Duration) -> MockSleepFuture {
MockSleepFuture {
duration,
deadline: None,
}
}
}
fn block_on<F: std::future::Future<Output = ()>>(fut: F) {
use std::sync::Arc;
use std::task::{Wake, Waker};
struct NoopWaker;
impl Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
}
let waker: Waker = Arc::new(NoopWaker).into();
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(()) => return,
Poll::Pending => std::thread::yield_now(),
}
}
}
#[test]
fn test_sleep() {
block_on(super::tests::test_sleep(MockClock));
}
#[test]
fn test_sleep_until() {
block_on(super::tests::test_sleep_until(MockClock));
}
#[test]
fn test_sleep_already_passed() {
block_on(super::tests::test_sleep_already_passed(MockClock));
}
#[test]
fn test_sleep_short_duration() {
block_on(super::tests::test_sleep_short_duration(MockClock));
}
#[test]
fn test_sleep_long_duration() {
block_on(super::tests::test_sleep_long_duration(MockClock));
}
#[test]
fn test_interval_basic() {
block_on(super::tests::test_interval_basic(MockClock));
}
#[test]
fn test_interval_reset() {
block_on(super::tests::test_interval_reset(MockClock));
}
#[test]
fn test_interval_reset_immediately() {
block_on(super::tests::test_interval_reset_immediately(MockClock));
}
#[test]
fn test_interval_multiple_ticks() {
block_on(super::tests::test_interval_multiple_ticks(MockClock));
}
#[test]
#[cfg(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
))]
fn test_sleep_builder_with_mock_clock() {
block_on(async {
let clock = MockClock;
let start = clock.now();
let sleep = sleep_with_clock(clock.clone(), Duration::from_millis(50));
sleep.await;
assert!(clock.elapsed(start) >= Duration::from_millis(50));
});
}
#[test]
#[cfg(not(any(
all(feature = "tokio", not(feature = "async-io")),
all(feature = "async-io", not(feature = "tokio"))
)))]
fn test_sleep_builder_with_mock_clock() {
block_on(async {
let clock = MockClock;
let start = clock.now();
let deadline = start + Duration::from_millis(50);
let sleep = SleepBuilder::<MockClock>::new(deadline.into())
.threshold(Duration::from_millis(5))
.build();
sleep.await;
assert!(clock.elapsed(start) >= Duration::from_millis(50));
});
}
#[test]
fn test_sleep_reset_with_mock_clock() {
block_on(async {
let clock = MockClock;
let start = clock.now();
let deadline = start + Duration::from_millis(30);
let mut sleep = sleep_until_with_clock(clock.clone(), deadline);
let new_deadline = start + Duration::from_millis(60);
Pin::new(&mut sleep).reset(new_deadline);
sleep.await;
assert!(clock.elapsed(start) >= Duration::from_millis(60));
});
}
#[test]
fn test_sleep_zero_duration() {
block_on(async {
let clock = MockClock;
let start = clock.now();
sleep_with_clock(clock.clone(), Duration::ZERO).await;
assert!(clock.elapsed(start) < Duration::from_millis(50));
});
}
}