use std::future::Future;
use std::time::Duration;
use tokio::time::{Timeout, timeout};
pub trait TimeoutExt: Sized {
fn with_timeout(self, duration: Duration) -> Timeout<Self>;
fn with_timeout_secs(self, secs: u64) -> Timeout<Self> {
self.with_timeout(Duration::from_secs(secs))
}
fn with_timeout_ms(self, ms: u64) -> Timeout<Self> {
self.with_timeout(Duration::from_millis(ms))
}
}
impl<F: Future> TimeoutExt for F {
fn with_timeout(self, duration: Duration) -> Timeout<Self> {
timeout(duration, self)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TimeoutConfig {
pub expect: Duration,
pub connect: Duration,
pub read: Duration,
pub write: Duration,
pub close: Duration,
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
expect: Duration::from_secs(30),
connect: Duration::from_secs(60),
read: Duration::from_secs(10),
write: Duration::from_secs(10),
close: Duration::from_secs(5),
}
}
}
impl TimeoutConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn expect(mut self, timeout: Duration) -> Self {
self.expect = timeout;
self
}
#[must_use]
pub const fn connect(mut self, timeout: Duration) -> Self {
self.connect = timeout;
self
}
#[must_use]
pub const fn read(mut self, timeout: Duration) -> Self {
self.read = timeout;
self
}
#[must_use]
pub const fn write(mut self, timeout: Duration) -> Self {
self.write = timeout;
self
}
#[must_use]
pub const fn close(mut self, timeout: Duration) -> Self {
self.close = timeout;
self
}
#[must_use]
pub const fn uniform(timeout: Duration) -> Self {
Self {
expect: timeout,
connect: timeout,
read: timeout,
write: timeout,
close: timeout,
}
}
#[must_use]
pub const fn none() -> Self {
let max = Duration::from_secs(u64::MAX / 2);
Self::uniform(max)
}
}
#[derive(Debug, Clone)]
pub struct Deadline {
deadline: tokio::time::Instant,
}
impl Deadline {
#[must_use]
pub fn from_now(duration: Duration) -> Self {
Self {
deadline: tokio::time::Instant::now() + duration,
}
}
#[must_use]
pub fn is_expired(&self) -> bool {
tokio::time::Instant::now() >= self.deadline
}
#[must_use]
pub fn remaining(&self) -> Duration {
self.deadline
.saturating_duration_since(tokio::time::Instant::now())
}
#[must_use]
pub fn has_time(&self) -> bool {
!self.is_expired()
}
pub async fn sleep(&self) {
let remaining = self.remaining();
if !remaining.is_zero() {
tokio::time::sleep(remaining).await;
}
}
pub fn apply<F: Future>(&self, future: F) -> Timeout<F> {
timeout(self.remaining(), future)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timeout_config_default() {
let config = TimeoutConfig::default();
assert_eq!(config.expect, Duration::from_secs(30));
}
#[test]
fn timeout_config_uniform() {
let config = TimeoutConfig::uniform(Duration::from_secs(5));
assert_eq!(config.expect, Duration::from_secs(5));
assert_eq!(config.connect, Duration::from_secs(5));
}
#[tokio::test]
async fn deadline_remaining() {
let deadline = Deadline::from_now(Duration::from_secs(10));
assert!(deadline.has_time());
assert!(deadline.remaining() > Duration::from_secs(9));
}
#[tokio::test]
async fn timeout_ext() {
let result = async { 42 }.with_timeout(Duration::from_secs(1)).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
}