mod debounced;
pub use debounced::{DebouncedTimeout, DebouncedTimeoutExt};
use std::time::{Duration, Instant};
use crate::{Stop, StopReason};
#[derive(Debug, Clone)]
pub struct WithTimeout<T> {
inner: T,
deadline: Instant,
}
impl<T: Stop> WithTimeout<T> {
#[inline]
pub fn new(inner: T, duration: Duration) -> Self {
Self {
inner,
deadline: Instant::now() + duration,
}
}
#[inline]
pub fn with_deadline(inner: T, deadline: Instant) -> Self {
Self { inner, deadline }
}
#[inline]
pub fn deadline(&self) -> Instant {
self.deadline
}
#[inline]
pub fn remaining(&self) -> Duration {
self.deadline.saturating_duration_since(Instant::now())
}
#[inline]
pub fn inner(&self) -> &T {
&self.inner
}
#[inline]
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: Stop> Stop for WithTimeout<T> {
#[inline]
fn check(&self) -> Result<(), StopReason> {
self.inner.check()?;
if Instant::now() >= self.deadline {
Err(StopReason::TimedOut)
} else {
Ok(())
}
}
#[inline]
fn should_stop(&self) -> bool {
self.inner.should_stop() || Instant::now() >= self.deadline
}
}
pub trait TimeoutExt: Stop + Sized {
#[inline]
fn with_timeout(self, duration: Duration) -> WithTimeout<Self> {
WithTimeout::new(self, duration)
}
#[inline]
fn with_deadline(self, deadline: Instant) -> WithTimeout<Self> {
WithTimeout::with_deadline(self, deadline)
}
}
impl<T: Stop> TimeoutExt for T {}
impl<T: Stop> WithTimeout<T> {
#[inline]
pub fn tighten(self, duration: Duration) -> Self {
let new_deadline = Instant::now() + duration;
Self {
inner: self.inner,
deadline: self.deadline.min(new_deadline),
}
}
#[inline]
pub fn tighten_deadline(self, deadline: Instant) -> Self {
Self {
inner: self.inner,
deadline: self.deadline.min(deadline),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::StopSource;
#[test]
fn with_timeout_basic() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_millis(100));
assert!(!stop.should_stop());
assert!(stop.check().is_ok());
std::thread::sleep(Duration::from_millis(150));
assert!(stop.should_stop());
assert_eq!(stop.check(), Err(StopReason::TimedOut));
}
#[test]
fn cancel_before_timeout() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_secs(60));
source.cancel();
assert!(stop.should_stop());
assert_eq!(stop.check(), Err(StopReason::Cancelled));
}
#[test]
fn timeout_tightens() {
let source = StopSource::new();
let stop = source
.as_ref()
.with_timeout(Duration::from_secs(60))
.tighten(Duration::from_secs(1));
let remaining = stop.remaining();
assert!(remaining < Duration::from_secs(2));
}
#[test]
fn with_deadline_basic() {
let source = StopSource::new();
let deadline = Instant::now() + Duration::from_millis(100);
let stop = source.as_ref().with_deadline(deadline);
assert!(!stop.should_stop());
std::thread::sleep(Duration::from_millis(150));
assert!(stop.should_stop());
}
#[test]
fn remaining_accuracy() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_secs(10));
let remaining = stop.remaining();
assert!(remaining > Duration::from_secs(9));
assert!(remaining <= Duration::from_secs(10));
}
#[test]
fn remaining_after_expiry() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(10));
assert_eq!(stop.remaining(), Duration::ZERO);
}
#[test]
fn inner_access() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_secs(10));
assert!(!stop.inner().should_stop());
source.cancel();
assert!(stop.inner().should_stop());
}
#[test]
fn into_inner() {
let source = StopSource::new();
let stop = source.as_ref().with_timeout(Duration::from_secs(10));
let inner = stop.into_inner();
assert!(!inner.should_stop());
}
#[test]
fn with_timeout_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<WithTimeout<crate::StopRef<'_>>>();
}
}