Skip to content

Commit

Permalink
Merge pull request #91 from communityvi/dont-capture-lifetimes
Browse files Browse the repository at this point in the history
Stop capturing the MockableClock lifetimes in the futures returned from their methods
  • Loading branch information
FSMaxB authored Jan 8, 2025
2 parents a279fc6 + f74cda1 commit 0c3135a
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 72 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[workspace]
members = ["async-time-mock*"]
members = [
"async-time-mock-async-std",
"async-time-mock-core",
"async-time-mock-smol",
"async-time-mock-tokio",
]
resolver = "2"
21 changes: 13 additions & 8 deletions async-time-mock-async-std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,18 @@ impl MockableClock {
}
}

pub async fn sleep(&self, duration: Duration) -> TimeHandlerGuard {
use MockableClock::*;
match self {
Real => {
sleep(duration).await;
TimeHandlerGuard::Real
pub fn sleep(&self, duration: Duration) -> impl Future<Output = TimeHandlerGuard> + Send + 'static {
let clock = self.clone();
async move {
use MockableClock::*;
match clock {
Real => {
sleep(duration).await;
TimeHandlerGuard::Real
}
#[cfg(feature = "mock")]
Mock(registry) => registry.sleep(duration).await.into(),
}
#[cfg(feature = "mock")]
Mock(registry) => registry.sleep(duration).await.into(),
}
}

Expand All @@ -85,6 +88,8 @@ impl MockableClock {
}
}

// NOTE: Can't currently transform to -> impl Future because whether it needs to implement `Send` or not depends on the future
// that is passed in.
pub async fn timeout<F, T>(&self, duration: Duration, future: F) -> Result<T, TimeoutError>
where
F: Future<Output = T>,
Expand Down
1 change: 1 addition & 0 deletions async-time-mock-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub use instant::Instant;
pub use interval::Interval;
pub use time_handler_guard::TimeHandlerGuard;
pub use timeout::{Elapsed, Timeout};
pub use timer::TimerListener;
pub use timer_registry::TimerRegistry;
1 change: 1 addition & 0 deletions async-time-mock-core/src/time_handler_guard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use event_listener::{Event, EventListener};

#[must_use = "TimeHandlerGuard must be kept until the timer has performed it's side-effects"]
#[derive(Debug)]
pub struct TimeHandlerGuard(Event);

impl TimeHandlerGuard {
Expand Down
43 changes: 27 additions & 16 deletions async-time-mock-core/src/timer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::time_handler_guard::TimeHandlerFinished;
use crate::TimeHandlerGuard;
use event_listener::{Event, EventListener};
use pin_project_lite::pin_project;
use std::future::Future;
use std::pin::Pin;
use std::task::{ready, Context, Poll};

pub(crate) struct Timer {
trigger: Event,
Expand All @@ -19,7 +23,7 @@ impl Timer {
},
TimerListener {
listener,
handler_guard,
handler_guard: Some(handler_guard),
},
)
}
Expand All @@ -34,20 +38,27 @@ impl Timer {
}
}

pub(crate) struct TimerListener {
listener: EventListener,
handler_guard: TimeHandlerGuard,
pin_project! {
#[derive(Debug)]
pub struct TimerListener {
#[pin]
listener: EventListener,
handler_guard: Option<TimeHandlerGuard>,
}
}

impl TimerListener {
pub(crate) async fn wait_until_triggered(self) -> TimeHandlerGuard {
let Self {
listener,
handler_guard,
} = self;
impl Future for TimerListener {
type Output = TimeHandlerGuard;

fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();

ready!(this.listener.poll(context));

listener.await;
handler_guard
match this.handler_guard.take() {
Some(handler_guard) => Poll::Ready(handler_guard),
None => Poll::Pending,
}
}
}

Expand All @@ -61,15 +72,15 @@ mod test {
async fn timer_should_trigger_timer_listener() {
let (timer, listener) = Timer::new();

let mut wait_until_triggered = pin!(listener.wait_until_triggered());
let mut listener = pin!(listener);
assert!(
poll_once(wait_until_triggered.as_mut()).await.is_none(),
poll_once(listener.as_mut()).await.is_none(),
"Future should have been pending before the timer is triggered",
);
let _ = timer.trigger();

assert!(
poll_once(wait_until_triggered.as_mut()).await.is_some(),
poll_once(listener.as_mut()).await.is_some(),
"Future should have been ready after timer was triggered"
);
}
Expand All @@ -79,7 +90,7 @@ mod test {
let (timer, listener) = Timer::new();

let time_handler_finished = timer.trigger();
let time_handler_guard = listener.wait_until_triggered().await;
let time_handler_guard = listener.await;

let mut waiter = pin!(time_handler_finished.wait());
assert!(
Expand Down
13 changes: 6 additions & 7 deletions async-time-mock-core/src/timer_registry.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::await_all::await_all;
use crate::time_handler_guard::TimeHandlerGuard;
use crate::timeout::Timeout;
use crate::timer::{Timer, TimerListener};
use crate::{Instant, Interval};
Expand Down Expand Up @@ -39,17 +38,17 @@ impl TimerRegistry {
/// (all sideeffects finished).
///
/// Roughly eqivalent to `async pub fn sleep(&self, duration: Duration) -> TimeHandlerGuard`.
pub fn sleep(&self, duration: Duration) -> impl Future<Output = TimeHandlerGuard> + Send + Sync + 'static {
pub fn sleep(&self, duration: Duration) -> TimerListener {
assert!(!duration.is_zero(), "Sleeping for zero time is not allowed");

let timer = {
let listener = {
let timers_by_time = self.timers_by_time.write().expect("RwLock was poisoned");
let wakeup_time = *self.current_time.read().expect("RwLock was poisoned") + duration;
Self::schedule_timer(timers_by_time, wakeup_time)
};
self.any_timer_scheduled_signal.notify(1);

timer.wait_until_triggered()
listener
}

/// Schedules a timer to expire at "Instant", once expired, returns
Expand All @@ -60,15 +59,15 @@ impl TimerRegistry {
///
/// # Panics
/// When `until` was created by a different instance of `TimerRegistry`.
pub fn sleep_until(&self, until: Instant) -> impl Future<Output = TimeHandlerGuard> + Send + Sync + 'static {
let timer = {
pub fn sleep_until(&self, until: Instant) -> TimerListener {
let listener = {
let timers_by_time = self.timers_by_time.write().expect("RwLock was poisoned");
let wakeup_time = until.into_duration(self.id);
Self::schedule_timer(timers_by_time, wakeup_time)
};
self.any_timer_scheduled_signal.notify(1);

timer.wait_until_triggered()
listener
}

/// Combines a future with a `sleep` timer. If the future finishes before
Expand Down
46 changes: 27 additions & 19 deletions async-time-mock-smol/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#![doc = include_str!("../README.md")]

use std::future::Future;
use std::time::{Duration, SystemTime};

mod instant;
Expand Down Expand Up @@ -55,32 +57,38 @@ impl MockableClock {
}
}

pub async fn sleep(&self, duration: Duration) -> TimeHandlerGuard {
use MockableClock::*;
match self {
Real => {
async_io::Timer::after(duration).await;
TimeHandlerGuard::Real
pub fn sleep(&self, duration: Duration) -> impl Future<Output = TimeHandlerGuard> + Send + 'static {
let clock = self.clone();
async move {
use MockableClock::*;
match clock {
Real => {
async_io::Timer::after(duration).await;
TimeHandlerGuard::Real
}
#[cfg(feature = "mock")]
Mock(registry) => registry.sleep(duration).await.into(),
}
#[cfg(feature = "mock")]
Mock(registry) => registry.sleep(duration).await.into(),
}
}

pub async fn sleep_until(&self, until: Instant) -> TimeHandlerGuard {
match (self, until) {
(MockableClock::Real, Instant::Real(until)) => {
async_io::Timer::at(until).await;
TimeHandlerGuard::Real
pub fn sleep_until(&self, until: Instant) -> impl Future<Output = TimeHandlerGuard> + Send + 'static {
let clock = self.clone();
async move {
match (clock, until) {
(MockableClock::Real, Instant::Real(until)) => {
async_io::Timer::at(until).await;
TimeHandlerGuard::Real
}
#[cfg(feature = "mock")]
(MockableClock::Mock(registry), Instant::Mock(until)) => registry.sleep_until(until).await.into(),
#[cfg(feature = "mock")]
_ => panic!("Clock and instant weren't compatible, both need to be either real or mocked"),
}
#[cfg(feature = "mock")]
(MockableClock::Mock(registry), Instant::Mock(until)) => registry.sleep_until(until).await.into(),
#[cfg(feature = "mock")]
_ => panic!("Clock and instant weren't compatible, both need to be either real or mocked"),
}
}

pub async fn interval(&self, period: Duration) -> Timer {
pub fn interval(&self, period: Duration) -> Timer {
use MockableClock::*;
match self {
Real => async_io::Timer::interval(period).into(),
Expand All @@ -89,7 +97,7 @@ impl MockableClock {
}
}

pub async fn interval_at(&self, start: Instant, period: Duration) -> Timer {
pub fn interval_at(&self, start: Instant, period: Duration) -> Timer {
match (self, start) {
(MockableClock::Real, Instant::Real(start)) => async_io::Timer::interval_at(start, period).into(),
#[cfg(feature = "mock")]
Expand Down
8 changes: 4 additions & 4 deletions async-time-mock-tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ rust-version = "1.70"


[dependencies]
async-time-mock-core = {version = "0.1", path = "../async-time-mock-core", optional = true}
async-time-mock-core = { version = "0.1", path = "../async-time-mock-core", optional = true }
futures-core = { version = "0.3", optional = true }
pin-project = "1"
tokio = {version = "1", features = ["time"]}
futures-core = {version = "0.3", optional = true}
tokio = { version = "1", features = ["time"] }

[dev-dependencies]
tokio = {version = "1", features = ["macros", "rt-multi-thread"]}
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

[features]
default = ["stream"]
Expand Down
27 changes: 12 additions & 15 deletions async-time-mock-tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
use std::future::Future;
use std::time::{Duration, SystemTime};

#[cfg(feature = "mock")]
pub use async_time_mock_core as core;

mod instant;
use crate::interval::Interval;
pub use instant::Instant;

#[cfg(feature = "mock")]
pub use async_time_mock_core as core;

mod elapsed;
mod interval;
mod sleep;
mod timeout;

pub use sleep::Sleep;
pub use timeout::Timeout;

#[derive(Clone)]
Expand Down Expand Up @@ -59,26 +62,20 @@ impl MockableClock {
}
}

pub async fn sleep(&self, duration: Duration) -> TimeHandlerGuard {
pub fn sleep(&self, duration: Duration) -> Sleep {
use MockableClock::*;
match self {
Real => {
tokio::time::sleep(duration).await;
TimeHandlerGuard::Real
}
Real => tokio::time::sleep(duration).into(),
#[cfg(feature = "mock")]
Mock(registry) => registry.sleep(duration).await.into(),
Mock(registry) => registry.sleep(duration).into(),
}
}

pub async fn sleep_until(&self, until: Instant) -> TimeHandlerGuard {
pub fn sleep_until(&self, until: Instant) -> Sleep {
match (self, until) {
(MockableClock::Real, Instant::Real(until)) => {
tokio::time::sleep_until(until).await;
TimeHandlerGuard::Real
}
(MockableClock::Real, Instant::Real(until)) => tokio::time::sleep_until(until).into(),
#[cfg(feature = "mock")]
(MockableClock::Mock(registry), Instant::Mock(until)) => registry.sleep_until(until).await.into(),
(MockableClock::Mock(registry), Instant::Mock(until)) => registry.sleep_until(until).into(),
#[cfg(feature = "mock")]
_ => panic!("Clock and instant weren't compatible, both need to be either real or mocked"),
}
Expand Down
50 changes: 50 additions & 0 deletions async-time-mock-tokio/src/sleep.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::TimeHandlerGuard;
#[cfg(feature = "mock")]
use async_time_mock_core::TimerListener;
use pin_project::pin_project;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::task::{ready, Context, Poll};

#[pin_project(project = SleepProjection)]
#[derive(Debug)]
pub enum Sleep {
Real(#[pin] tokio::time::Sleep),
#[cfg(feature = "mock")]
Mock(#[pin] TimerListener),
}

impl From<tokio::time::Sleep> for Sleep {
fn from(sleep: tokio::time::Sleep) -> Self {
Self::Real(sleep)
}
}

#[cfg(feature = "mock")]
impl From<TimerListener> for Sleep {
fn from(listener: TimerListener) -> Self {
Self::Mock(listener)
}
}

impl Future for Sleep {
type Output = TimeHandlerGuard;

fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();

use SleepProjection::*;
match this {
Real(sleep) => {
ready!(sleep.poll(context));
Poll::Ready(TimeHandlerGuard::Real)
}
#[cfg(feature = "mock")]
Mock(listener) => {
let guard = ready!(listener.poll(context));
Poll::Ready(guard.into())
}
}
}
}
2 changes: 0 additions & 2 deletions async-time-mock-tokio/src/timeout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ impl<T: Debug> Debug for Timeout<T> {
}
}

// implementing `Unpin` is currently impossible because the underlying async_time_mock_core::Timeout doesn't allow it (`sleep` is an async function)

impl<T> Future for Timeout<T>
where
T: Future,
Expand Down

0 comments on commit 0c3135a

Please sign in to comment.