use std::pin::Pin;
use std::time::Duration;
use tokio::time::{Instant, Sleep};
pub struct IdleGuard {
timeout: Duration,
sleep: Pin<Box<Sleep>>,
}
impl core::fmt::Debug for IdleGuard {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IdleGuard")
.field("timeout", &self.timeout)
.finish_non_exhaustive()
}
}
impl IdleGuard {
#[must_use]
pub fn new(timeout: Duration) -> Self {
Self {
timeout,
sleep: Box::pin(tokio::time::sleep(timeout)),
}
}
#[must_use]
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn reset(&mut self) {
let deadline = Instant::now() + self.timeout;
self.sleep.as_mut().reset(deadline);
}
pub fn tick(&mut self) -> &mut Pin<Box<Sleep>> {
&mut self.sleep
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[tokio::test(start_paused = true)]
async fn idle_guard_fires_after_timeout() {
let mut guard = IdleGuard::new(Duration::from_millis(100));
tokio::time::advance(Duration::from_millis(101)).await;
guard.tick().await;
}
#[tokio::test(start_paused = true)]
async fn idle_guard_reset_extends_window() {
let mut guard = IdleGuard::new(Duration::from_millis(100));
tokio::time::advance(Duration::from_millis(80)).await;
guard.reset();
tokio::time::advance(Duration::from_millis(80)).await;
tokio::select! {
biased;
_ = guard.tick() => panic!("idle guard fired prematurely"),
_ = tokio::time::sleep(Duration::from_millis(0)) => {}
}
tokio::time::advance(Duration::from_millis(30)).await;
guard.tick().await;
}
}