/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ error::Error, fmt, future::Future, io, pin::Pin, task::{Context, Poll}, time::Duration, }; use pin_project_lite::pin_project; use smol::Timer; /// Awaits an I/O future or times out after a duration of time. /// /// If you want to await a non I/O future consider using /// `timeout()` instead. /// /// # Examples /// /// ```no_run /// # fn main() -> std::io::Result<()> { smol::block_on(async { /// # /// use std::time::Duration; /// use std::io; /// /// io_timeout(Duration::from_secs(5), async { /// let stdin = io::stdin(); /// let mut line = String::new(); /// let n = stdin.read_line(&mut line)?; /// Ok(()) /// }) /// .await?; /// # /// # Ok(()) }) } pub async fn io_timeout(dur: Duration, f: F) -> io::Result where F: Future>, { Timeout { timeout: Timer::after(dur), future: f }.await } pin_project! { #[derive(Debug)] pub struct Timeout where F: Future>, { #[pin] future: F, #[pin] timeout: Timer, } } impl Future for Timeout where F: Future>, { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); match this.future.poll(cx) { Poll::Pending => {} other => return other, } if this.timeout.poll(cx).is_ready() { let err = Err(io::Error::new(io::ErrorKind::TimedOut, "future timed out")); Poll::Ready(err) } else { Poll::Pending } } } /// Awaits a future or times out after a duration of time. /// /// If you want to await an I/O future consider using /// `io_timeout` instead. /// /// # Examples /// /// ``` /// # fn main() -> std::io::Result<()> { smol::block_on(async { /// # /// use std::time::Duration; /// use smol::future; /// /// let never = future::pending::<()>(); /// let dur = Duration::from_millis(5); /// assert!(timeout(dur, never).await.is_err()); /// # /// # Ok(()) }) } /// ``` pub async fn timeout(dur: Duration, f: F) -> Result where F: Future, { TimeoutFuture::new(f, dur).await } pin_project! { /// A future that times out after a duration of time. pub struct TimeoutFuture { #[pin] future: F, #[pin] delay: Timer, } } impl TimeoutFuture { #[allow(dead_code)] pub(super) fn new(future: F, dur: Duration) -> TimeoutFuture { TimeoutFuture { future, delay: Timer::after(dur) } } } impl Future for TimeoutFuture { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); match this.future.poll(cx) { Poll::Ready(v) => Poll::Ready(Ok(v)), Poll::Pending => match this.delay.poll(cx) { Poll::Ready(_) => Poll::Ready(Err(TimeoutError { _private: () })), Poll::Pending => Poll::Pending, }, } } } /// An error returned when a future times out. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TimeoutError { _private: (), } impl Error for TimeoutError {} impl fmt::Display for TimeoutError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { "future has timed out".fmt(f) } }