use super::{
AbortHandle, Abortable, AssertUnwindSafe, Cell, DriverTask, Duration, Future, FutureExt,
LocalBoxFuture, LocalPool, LocalSpawnExt, LocalSpawner, LocalTask, Poll, Rc, RefCell,
RuntimeDriver, SpawnError, TaskOutcome, oneshot,
};
#[derive(Debug)]
pub(super) struct DeterministicState {
pub(super) now: Cell<Duration>,
pub(super) shutdown_requested: Cell<bool>,
pub(super) jitter: Cell<Duration>,
pub(super) pool: RefCell<Option<LocalPool>>,
pub(super) spawner: LocalSpawner,
pub(super) timers: RefCell<Vec<(Duration, oneshot::Sender<()>)>>,
}
#[derive(Clone, Debug)]
pub struct DeterministicDriver {
pub(super) state: Rc<DeterministicState>,
}
impl DeterministicDriver {
pub fn new() -> Self {
let pool = LocalPool::new();
let spawner = pool.spawner();
Self {
state: Rc::new(DeterministicState {
now: Cell::new(Duration::ZERO),
shutdown_requested: Cell::new(false),
jitter: Cell::new(Duration::ZERO),
pool: RefCell::new(Some(pool)),
spawner,
timers: RefCell::new(Vec::new()),
}),
}
}
pub fn run<F: Future>(&self, future: F) -> F::Output {
let mut pool = self
.state
.pool
.borrow_mut()
.take()
.expect("deterministic Driver cannot run recursively");
let output = pool.run_until(future);
self.state.pool.replace(Some(pool));
output
}
pub fn advance(&self, duration: Duration) {
self.state.now.set(self.state.now.get() + duration);
let now = self.state.now.get();
let mut timers = self.state.timers.borrow_mut();
let mut pending = Vec::with_capacity(timers.len());
for (deadline, wakeup) in timers.drain(..) {
if deadline <= now {
let _ = wakeup.send(());
} else {
pending.push((deadline, wakeup));
}
}
*timers = pending;
}
pub fn request_shutdown(&self) {
self.state.shutdown_requested.set(true);
}
pub fn now(&self) -> Duration {
self.state.now.get()
}
pub fn set_jitter(&self, jitter: Duration) {
self.state.jitter.set(jitter);
}
#[must_use]
pub fn with_jitter(self, jitter: Duration) -> Self {
self.set_jitter(jitter);
self
}
}
impl Default for DeterministicDriver {
fn default() -> Self {
Self::new()
}
}
impl RuntimeDriver for DeterministicDriver {
fn now(&self) -> Duration {
self.now()
}
fn sleep_until(&self, deadline: Duration) -> LocalBoxFuture<'static, ()> {
if deadline <= self.now() {
return Box::pin(futures::future::ready(()));
}
let (wakeup, sleeper) = oneshot::channel();
self.state.timers.borrow_mut().push((deadline, wakeup));
Box::pin(async move {
let _ = sleeper.await;
})
}
fn yield_now(&self) -> LocalBoxFuture<'static, ()> {
let mut yielded = false;
Box::pin(futures::future::poll_fn(move |context| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
context.waker().wake_by_ref();
Poll::Pending
}
}))
}
fn jitter(&self, maximum: Duration) -> Duration {
self.state.jitter.get().min(maximum)
}
fn spawn_local(&self, task: LocalTask) -> Result<DriverTask, SpawnError> {
let (abort, registration) = AbortHandle::new_pair();
let (completed, completion) = oneshot::channel();
self.state.spawner.spawn_local(async move {
let outcome = match AssertUnwindSafe(Abortable::new(task, registration))
.catch_unwind()
.await
{
Ok(Ok(())) => TaskOutcome::Completed,
Ok(Err(_)) => TaskOutcome::Cancelled,
Err(_) => TaskOutcome::Failed,
};
let _ = completed.send(outcome);
})?;
Ok(DriverTask::new(abort, completion))
}
fn shutdown_requested(&self) -> bool {
self.state.shutdown_requested.get()
}
}