├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── src ├── async_io.rs ├── async_std.rs ├── deadline.rs ├── future.rs ├── lib.rs ├── stop_source.rs ├── stream.rs └── tokio.rs └── tests └── tests.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - staging 8 | - trying 9 | - master 10 | 11 | env: 12 | RUSTFLAGS: -Dwarnings 13 | 14 | jobs: 15 | build_and_test: 16 | name: Build and test 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@master 21 | 22 | - name: Install nightly 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: nightly 26 | override: true 27 | 28 | - name: tests async-io 29 | uses: actions-rs/cargo@v1 30 | with: 31 | command: test 32 | args: --features async-io 33 | 34 | - name: tests tokio 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: test 38 | args: --features tokio 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stop-token" 3 | version = "0.7.0" 4 | authors = ["Aleksey Kladov ", "Yoshua Wuyts "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/async-rs/stop-token" 8 | homepage = "https://docs.rs/stop-token" 9 | documentation = "https://docs.rs/stop-token" 10 | 11 | description = "Experimental cooperative cancellation for async Rust" 12 | 13 | [package.metadata.docs.rs] 14 | features = ["docs"] 15 | rustdoc-args = ["--cfg", "feature=\"docs\""] 16 | 17 | [features] 18 | all = ["tokio", "async-io", "async-std"] 19 | docs = ["async-io"] 20 | 21 | [dependencies] 22 | async-channel = "1.6.1" 23 | async-global-executor = { version = "2.0.2", optional = true } 24 | async-io = { version = "1.6.0", optional = true } 25 | async-std = { version = "1.10.0", optional = true } 26 | cfg-if = "1.0.0" 27 | futures-core = "0.3.17" 28 | pin-project-lite = "0.2.0" 29 | tokio = { version = "1.9.0", features = ["time"], optional = true } 30 | 31 | [dev-dependencies] 32 | async-std = { version = "1.10.0", features = ["attributes"] } 33 | tokio = { version = "1.9.0", features = ["rt", "macros"] } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

stop-token

2 |
3 | 4 | Cooperative cancellation for async Rust 5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 | 13 | Crates.io version 15 | 16 | 17 | 18 | Download 20 | 21 | 22 | 23 | docs.rs docs 25 | 26 |
27 | 28 |
29 |

30 | 31 | API Docs 32 | 33 | | 34 | 35 | Releases 36 | 37 | | 38 | 39 | Contributing 40 | 41 |

42 |
43 | 44 | See crate docs for details 45 | 46 | You can use this crate to create a deadline received through a `StopToken`: 47 | 48 | ```rust 49 | use async_std::prelude::*; 50 | use async_std::{stream, task}; 51 | 52 | use stop_token::prelude::*; 53 | use stop_token::StopSource; 54 | 55 | use std::time::Duration; 56 | 57 | #[async_std::main] 58 | async fn main() { 59 | // Create a stop source and generate a token. 60 | let src = StopSource::new(); 61 | let deadline = src.token(); 62 | 63 | // When stop source is dropped, the loop will stop. 64 | // Move the source to a task, and drop it after 100 millis. 65 | task::spawn(async move { 66 | task::sleep(Duration::from_millis(100)).await; 67 | drop(src); 68 | }); 69 | 70 | // Create a stream that generates numbers until 71 | // it receives a signal it needs to stop. 72 | let mut work = stream::repeat(12u8).timeout_at(deadline); 73 | 74 | // Loop over each item in the stream. 75 | while let Some(Ok(ev)) = work.next().await { 76 | println!("{}", ev); 77 | } 78 | } 79 | ``` 80 | 81 | Or `Instant` to create a `time`-based deadline: 82 | 83 | ```rust 84 | use async_std::prelude::*; 85 | use async_std::stream; 86 | 87 | use stop_token::prelude::*; 88 | 89 | use std::time::{Instant, Duration}; 90 | 91 | #[async_std::main] 92 | async fn main() { 93 | // Create a stream that generates numbers for 100 millis. 94 | let deadline = Instant::now() + Duration::from_millis(100); 95 | let mut work = stream::repeat(12u8).timeout_at(deadline); 96 | 97 | // Loop over each item in the stream. 98 | while let Some(Ok(ev)) = work.next().await { 99 | println!("{}", ev); 100 | } 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /src/async_io.rs: -------------------------------------------------------------------------------- 1 | //! Create deadlines from `Instant`. 2 | //! 3 | //! # Features 4 | //! 5 | //! This module is empty when no features are enabled. To implement deadlines 6 | //! for `Instant` you can enable one of the following features: 7 | //! 8 | //! - `async-io`: use this when using the `async-std` or `smol` runtimes. 9 | //! - `tokio`: use this when using the `tokio` runtime. 10 | //! 11 | //! # Examples 12 | //! 13 | //! ``` 14 | //! use std::time::Instant; 15 | //! use async_std::prelude::*; 16 | //! use stop_token::prelude::*; 17 | //! use stop_token::StopToken; 18 | //! 19 | //! struct Event; 20 | //! 21 | //! async fn do_work(work: impl Stream + Unpin, deadline: Instant) { 22 | //! let mut work = work.timeout_at(deadline); 23 | //! while let Some(Ok(event)) = work.next().await { 24 | //! process_event(event).await 25 | //! } 26 | //! } 27 | //! 28 | //! async fn process_event(_event: Event) { 29 | //! } 30 | //! ``` 31 | 32 | use async_io::Timer; 33 | use std::future::Future; 34 | use std::pin::Pin; 35 | use std::task::{Context, Poll}; 36 | use std::time::Instant; 37 | 38 | use pin_project_lite::pin_project; 39 | 40 | pin_project! { 41 | /// A future that times out after a duration of time. 42 | #[must_use = "Futures do nothing unless polled or .awaited"] 43 | #[derive(Debug)] 44 | pub(crate) struct Deadline { 45 | instant: Instant, 46 | #[pin] 47 | delay: Timer, 48 | } 49 | } 50 | 51 | impl Clone for Deadline { 52 | fn clone(&self) -> Self { 53 | Self { 54 | instant: self.instant, 55 | delay: Timer::at(self.instant), 56 | } 57 | } 58 | } 59 | 60 | impl Future for Deadline { 61 | type Output = (); 62 | 63 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 64 | let this = self.project(); 65 | match this.delay.poll(cx) { 66 | Poll::Ready(_) => Poll::Ready(()), 67 | Poll::Pending => Poll::Pending, 68 | } 69 | } 70 | } 71 | 72 | impl Into for std::time::Instant { 73 | fn into(self) -> crate::Deadline { 74 | let deadline = Deadline { 75 | instant: self, 76 | delay: Timer::at(self), 77 | }; 78 | crate::Deadline { 79 | kind: crate::deadline::DeadlineKind::AsyncIo { t: deadline }, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/async_std.rs: -------------------------------------------------------------------------------- 1 | use async_std::task::JoinHandle; 2 | 3 | /// Extend the `Task` type `until` method. 4 | pub trait TaskExt { 5 | /// Run a future until it resolves, or until a deadline is hit. 6 | fn timeout_at(self, target: T) -> TimeoutAt 7 | where 8 | Self: Sized, 9 | T: Into, 10 | { 11 | TimeoutAt { 12 | deadline: target.into(), 13 | join_handle: self, 14 | } 15 | } 16 | } 17 | 18 | impl JoinHandleExt for JoinHandle {} 19 | 20 | pin_project! { 21 | /// Run a future until it resolves, or until a deadline is hit. 22 | /// 23 | /// This method is returned by [`FutureExt::deadline`]. 24 | #[must_use = "Futures do nothing unless polled or .awaited"] 25 | #[derive(Debug)] 26 | pub struct TimeoutAt { 27 | #[pin] 28 | futur_handlee: F, 29 | #[pin] 30 | deadline: Deadline, 31 | } 32 | } 33 | 34 | impl Future for TimeoutAt 35 | where 36 | F: Future, 37 | { 38 | type Output = Result; 39 | 40 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 41 | let this = self.project(); 42 | if let Poll::Ready(()) = this.deadline.poll(cx) { 43 | let _fut = this.join_handle.cancel(); 44 | return Poll::Ready(Err(TimedOutError::new())); 45 | } 46 | match this.join_handle.poll(cx) { 47 | Poll::Pending => Poll::Pending, 48 | Poll::Ready(it) => Poll::Ready(Ok(it)), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/deadline.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::{ 3 | error::Error, 4 | future::Future, 5 | io, 6 | pin::Pin, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use crate::StopToken; 11 | 12 | /// An error returned when a future times out. 13 | #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] 14 | pub struct TimedOutError { 15 | _private: (), 16 | } 17 | 18 | impl fmt::Debug for TimedOutError { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | f.debug_struct("TimeoutError").finish() 21 | } 22 | } 23 | 24 | impl TimedOutError { 25 | pub(crate) fn new() -> Self { 26 | Self { _private: () } 27 | } 28 | } 29 | 30 | impl Error for TimedOutError {} 31 | 32 | impl Into for TimedOutError { 33 | fn into(self) -> io::Error { 34 | io::Error::new(io::ErrorKind::TimedOut, "Future has timed out") 35 | } 36 | } 37 | 38 | impl fmt::Display for TimedOutError { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | "Future has timed out".fmt(f) 41 | } 42 | } 43 | 44 | pin_project_lite::pin_project! { 45 | /// A future that times out after a duration of time. 46 | #[must_use = "Futures do nothing unless polled or .awaited"] 47 | #[derive(Debug)] 48 | pub struct Deadline { 49 | #[pin] 50 | pub(crate) kind: DeadlineKind, 51 | } 52 | } 53 | 54 | cfg_if::cfg_if! { 55 | if #[cfg(all(feature = "tokio", feature = "async-io"))] { 56 | pin_project_lite::pin_project! { 57 | #[project = DeadlineKindProj] 58 | #[derive(Debug, Clone)] 59 | pub(crate) enum DeadlineKind { 60 | StopToken{ #[pin]t: StopToken}, 61 | Tokio{#[pin]t: crate::tokio::Deadline}, 62 | AsyncIo{#[pin]t: crate::async_io::Deadline}, 63 | } 64 | } 65 | } else if #[cfg(feature = "tokio")] { 66 | pin_project_lite::pin_project! { 67 | #[project = DeadlineKindProj] 68 | #[derive(Debug, Clone)] 69 | pub(crate) enum DeadlineKind { 70 | StopToken{ #[pin]t: StopToken}, 71 | Tokio{#[pin]t: crate::tokio::Deadline}, 72 | } 73 | } 74 | } else if #[cfg(feature = "async-io")] { 75 | pin_project_lite::pin_project! { 76 | #[project = DeadlineKindProj] 77 | #[derive(Debug, Clone)] 78 | pub(crate) enum DeadlineKind { 79 | StopToken{ #[pin]t: StopToken}, 80 | AsyncIo{#[pin]t: crate::async_io::Deadline}, 81 | } 82 | } 83 | } else { 84 | pin_project_lite::pin_project! { 85 | #[project = DeadlineKindProj] 86 | #[derive(Debug, Clone)] 87 | pub(crate) enum DeadlineKind { 88 | StopToken{ #[pin]t: StopToken}, 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl Clone for Deadline { 95 | fn clone(&self) -> Self { 96 | Self { 97 | kind: self.kind.clone(), 98 | } 99 | } 100 | } 101 | 102 | impl Future for Deadline { 103 | type Output = (); 104 | 105 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 106 | match self.project().kind.project() { 107 | DeadlineKindProj::StopToken { t } => t.poll(cx), 108 | #[cfg(feature = "tokio")] 109 | DeadlineKindProj::Tokio { t } => t.poll(cx), 110 | #[cfg(feature = "async-io")] 111 | DeadlineKindProj::AsyncIo { t } => t.poll(cx), 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/future.rs: -------------------------------------------------------------------------------- 1 | //! Extension methods and types for the `Future` trait. 2 | 3 | use crate::{deadline::TimedOutError, Deadline}; 4 | use core::future::Future; 5 | use core::pin::Pin; 6 | 7 | use pin_project_lite::pin_project; 8 | use std::task::{Context, Poll}; 9 | 10 | /// Extend the `Future` trait with the `until` method. 11 | pub trait FutureExt: Future { 12 | /// Run a future until it resolves, or until a deadline is hit. 13 | fn timeout_at(self, target: T) -> TimeoutAt 14 | where 15 | Self: Sized, 16 | T: Into, 17 | { 18 | TimeoutAt { 19 | deadline: target.into(), 20 | future: self, 21 | } 22 | } 23 | } 24 | 25 | impl FutureExt for F {} 26 | 27 | pin_project! { 28 | /// Run a future until it resolves, or until a deadline is hit. 29 | /// 30 | /// This method is returned by [`FutureExt::deadline`]. 31 | #[must_use = "Futures do nothing unless polled or .awaited"] 32 | #[derive(Debug)] 33 | pub struct TimeoutAt { 34 | #[pin] 35 | future: F, 36 | #[pin] 37 | deadline: Deadline, 38 | } 39 | } 40 | 41 | impl Future for TimeoutAt 42 | where 43 | F: Future, 44 | { 45 | type Output = Result; 46 | 47 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 48 | let this = self.project(); 49 | if let Poll::Ready(()) = this.deadline.poll(cx) { 50 | return Poll::Ready(Err(TimedOutError::new())); 51 | } 52 | match this.future.poll(cx) { 53 | Poll::Pending => Poll::Pending, 54 | Poll::Ready(it) => Poll::Ready(Ok(it)), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cooperative cancellation for async Rust. 2 | //! 3 | //! # Status 4 | //! 5 | //! Experimental. The library works as is, breaking changes will bump major 6 | //! version, but there are no guarantees of long-term support. 7 | //! 8 | //! # Motivation 9 | //! 10 | //! Rust futures come with a build-in cancellation mechanism: dropping a future 11 | //! prevents any further progress of the future. This is a *hard* cancellation 12 | //! mechanism, meaning that the future can potentially be cancelled at any 13 | //! `.await` expression. 14 | //! 15 | //! Sometimes, you need are more fine-grained cancellation mechanism. Imagine a 16 | //! chat server that relays messages to peers. Sending a single message 17 | //! potentially needs several writes on the socket object. That means that, if 18 | //! we use hard-cancellation for client connections, a connection can be 19 | //! abruptly terminated mid-message (even mid-emoji, if we are especially 20 | //! unlucky). What we need here is cooperative cancellation: client connection 21 | //! should be gracefully shutdown *between* the messages. 22 | //! 23 | //! More generally, if you have an event processing loop like 24 | //! 25 | //! ```ignore 26 | //! while let Some(event) = work.next().await { 27 | //! process_event(event).await 28 | //! } 29 | //! ``` 30 | //! 31 | //! you usually want to maintain an invariant that each event is either fully 32 | //! processed or not processed at all. If you need to terminate this loop early, 33 | //! you want to do this *between* iteration. 34 | //! 35 | //! # Usage 36 | //! 37 | //! You can use this crate to create a deadline received through a 38 | //! [`StopToken`]. You can think of a `StopSource` + `StopToken` as a 39 | //! single-producer, multi-consumer channel that receives a single message to 40 | //! "stop" when the producer is dropped: 41 | //! 42 | //! ``` 43 | //! use async_std::prelude::*; 44 | //! use async_std::{stream, task}; 45 | //! 46 | //! use stop_token::prelude::*; 47 | //! use stop_token::StopSource; 48 | //! 49 | //! use std::time::Duration; 50 | //! 51 | //! #[async_std::main] 52 | //! async fn main() { 53 | //! // Create a stop source and generate a token. 54 | //! let src = StopSource::new(); 55 | //! let stop = src.token(); 56 | //! 57 | //! // When stop source is dropped, the loop will stop. 58 | //! // Move the source to a task, and drop it after 100 millis. 59 | //! task::spawn(async move { 60 | //! task::sleep(Duration::from_millis(100)).await; 61 | //! drop(src); 62 | //! }); 63 | //! 64 | //! // Create a stream that generates numbers until 65 | //! // it receives a signal it needs to stop. 66 | //! let mut work = stream::repeat(12u8).timeout_at(stop); 67 | //! 68 | //! // Loop over each item in the stream. 69 | //! while let Some(Ok(ev)) = work.next().await { 70 | //! println!("{}", ev); 71 | //! } 72 | //! } 73 | //! ``` 74 | //! 75 | //! Or `Instant` to create a `time`-based deadline: 76 | //! 77 | //! ``` 78 | //! # #![allow(dead_code)] 79 | //! use async_std::prelude::*; 80 | //! use async_std::stream; 81 | //! 82 | //! use stop_token::prelude::*; 83 | //! 84 | //! use std::time::{Duration, Instant}; 85 | //! 86 | //! # #[cfg(feature = "tokio")] 87 | //! # fn main() {} 88 | //! # #[cfg(not(feature = "tokio"))] 89 | //! #[async_std::main] 90 | //! async fn main() { 91 | //! // Create a stream that generates numbers for 100 millis. 92 | //! let stop = Instant::now() + Duration::from_millis(100); 93 | //! let mut work = stream::repeat(12u8).timeout_at(stop); 94 | //! 95 | //! // Loop over each item in the stream. 96 | //! while let Some(Ok(ev)) = work.next().await { 97 | //! println!("{}", ev); 98 | //! } 99 | //! } 100 | //! ``` 101 | //! 102 | //! # Features 103 | //! 104 | //! The `time` submodule is empty when no features are enabled. To implement `Into` 105 | //! for `Instant` you can enable one of the following features: 106 | //! 107 | //! - `async-io`: for use with the `async-std` or `smol` runtimes. 108 | //! - `tokio`: for use with the `tokio` runtime. 109 | //! 110 | //! # Lineage 111 | //! 112 | //! The cancellation system is a subset of `C#` [`CancellationToken / CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads). 113 | //! The `StopToken / StopTokenSource` terminology is borrowed from [C++ paper P0660](https://wg21.link/p0660). 114 | 115 | #![forbid(unsafe_code)] 116 | #![deny(missing_debug_implementations, nonstandard_style, rust_2018_idioms)] 117 | #![warn(missing_docs, future_incompatible, unreachable_pub)] 118 | 119 | pub mod future; 120 | pub mod stream; 121 | 122 | #[cfg(any(feature = "async-io", feature = "docs"))] 123 | pub mod async_io; 124 | #[cfg(feature = "async_std")] 125 | pub mod async_std; 126 | #[cfg(feature = "tokio")] 127 | pub mod tokio; 128 | 129 | mod deadline; 130 | mod stop_source; 131 | 132 | pub use deadline::{Deadline, TimedOutError}; 133 | pub use stop_source::{StopSource, StopToken}; 134 | 135 | /// A prelude for `stop-token`. 136 | pub mod prelude { 137 | pub use crate::future::FutureExt as _; 138 | pub use crate::stream::StreamExt as _; 139 | } 140 | -------------------------------------------------------------------------------- /src/stop_source.rs: -------------------------------------------------------------------------------- 1 | use core::future::Future; 2 | use core::pin::Pin; 3 | use core::task::{Context, Poll}; 4 | 5 | use async_channel::{bounded, Receiver, Sender}; 6 | use futures_core::stream::Stream; 7 | 8 | use crate::Deadline; 9 | 10 | enum Never {} 11 | 12 | /// `StopSource` produces `StopToken` and cancels all of its tokens on drop. 13 | /// 14 | /// # Example: 15 | /// 16 | /// ```ignore 17 | /// let source = StopSource::new(); 18 | /// let token = source.token(); 19 | /// schedule_some_work(token); 20 | /// drop(source); // At this point, scheduled work notices that it is canceled. 21 | /// ``` 22 | #[derive(Debug)] 23 | pub struct StopSource { 24 | /// Solely for `Drop`. 25 | _chan: Sender, 26 | stop_token: StopToken, 27 | } 28 | 29 | /// `StopToken` is a future which completes when the associated `StopSource` is dropped. 30 | #[derive(Debug, Clone)] 31 | pub struct StopToken { 32 | chan: Receiver, 33 | } 34 | 35 | impl Default for StopSource { 36 | fn default() -> StopSource { 37 | let (sender, receiver) = bounded::(1); 38 | 39 | StopSource { 40 | _chan: sender, 41 | stop_token: StopToken { chan: receiver }, 42 | } 43 | } 44 | } 45 | 46 | impl StopSource { 47 | /// Creates a new `StopSource`. 48 | pub fn new() -> StopSource { 49 | StopSource::default() 50 | } 51 | 52 | /// Produces a new `StopToken`, associated with this source. 53 | /// 54 | /// Once the source is destroyed, `StopToken` future completes. 55 | pub fn token(&self) -> StopToken { 56 | self.stop_token.clone() 57 | } 58 | } 59 | 60 | impl Into for StopToken { 61 | fn into(self) -> Deadline { 62 | Deadline { 63 | kind: crate::deadline::DeadlineKind::StopToken { t: self }, 64 | } 65 | } 66 | } 67 | 68 | impl Future for StopToken { 69 | type Output = (); 70 | 71 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 72 | let chan = Pin::new(&mut self.chan); 73 | match Stream::poll_next(chan, cx) { 74 | Poll::Pending => Poll::Pending, 75 | Poll::Ready(Some(never)) => match never {}, 76 | Poll::Ready(None) => Poll::Ready(()), 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | //! Extension methods and types for the `Stream` trait. 2 | 3 | use crate::{deadline::TimedOutError, Deadline}; 4 | use core::future::Future; 5 | use core::pin::Pin; 6 | 7 | use futures_core::Stream; 8 | use pin_project_lite::pin_project; 9 | use std::task::{Context, Poll}; 10 | 11 | /// Extend the `Stream` trait with the `until` method. 12 | pub trait StreamExt: Stream { 13 | /// Applies the token to the `stream`, such that the resulting stream 14 | /// produces no more items once the token becomes cancelled. 15 | fn timeout_at(self, target: T) -> TimeoutAt 16 | where 17 | Self: Sized, 18 | T: Into, 19 | { 20 | TimeoutAt { 21 | stream: self, 22 | deadline: target.into(), 23 | } 24 | } 25 | } 26 | 27 | impl StreamExt for S {} 28 | 29 | pin_project! { 30 | /// Run a future until it resolves, or until a deadline is hit. 31 | /// 32 | /// This method is returned by [`FutureExt::deadline`]. 33 | #[must_use = "Futures do nothing unless polled or .awaited"] 34 | #[derive(Debug)] 35 | pub struct TimeoutAt { 36 | #[pin] 37 | stream: S, 38 | #[pin] 39 | deadline: Deadline, 40 | } 41 | } 42 | 43 | impl TimeoutAt { 44 | /// Unwraps this `Stop` stream, returning the underlying stream. 45 | pub fn into_inner(self) -> S { 46 | self.stream 47 | } 48 | } 49 | 50 | impl Stream for TimeoutAt 51 | where 52 | S: Stream, 53 | { 54 | type Item = Result; 55 | 56 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 57 | let this = self.project(); 58 | if let Poll::Ready(()) = this.deadline.poll(cx) { 59 | return Poll::Ready(Some(Err(TimedOutError::new()))); 60 | } 61 | this.stream.poll_next(cx).map(|el| el.map(|el| Ok(el))) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/tokio.rs: -------------------------------------------------------------------------------- 1 | //! Create deadlines from `Instant`. 2 | //! 3 | //! # Features 4 | //! 5 | //! This module is empty when no features are enabled. To implement deadlines 6 | //! for `Instant` and `Duration` you can enable one of the following features: 7 | //! 8 | //! - `async-io`: use this when using the `async-std` or `smol` runtimes. 9 | //! - `tokio`: use this when using the `tokio` runtime. 10 | //! 11 | //! # Examples 12 | 13 | use std::future::{pending, Future, Pending}; 14 | use std::pin::Pin; 15 | use std::task::{Context, Poll}; 16 | use tokio::time::{timeout_at, Instant as TokioInstant, Timeout}; 17 | 18 | /// A future that times out after a duration of time. 19 | #[must_use = "Futures do nothing unless polled or .awaited"] 20 | #[derive(Debug)] 21 | pub(crate) struct Deadline { 22 | instant: TokioInstant, 23 | delay: Pin>>>, 24 | } 25 | 26 | impl Clone for Deadline { 27 | fn clone(&self) -> Self { 28 | let instant = self.instant.clone(); 29 | Self { 30 | instant, 31 | delay: Box::pin(timeout_at(instant, pending())), 32 | } 33 | } 34 | } 35 | 36 | impl Future for Deadline { 37 | type Output = (); 38 | 39 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 40 | match Pin::new(&mut self.delay).poll(cx) { 41 | Poll::Ready(_) => Poll::Ready(()), 42 | Poll::Pending => Poll::Pending, 43 | } 44 | } 45 | } 46 | 47 | impl Into for tokio::time::Instant { 48 | fn into(self) -> crate::Deadline { 49 | let instant = TokioInstant::from(self); 50 | let deadline = Deadline { 51 | instant, 52 | delay: Box::pin(timeout_at(instant, pending())), 53 | }; 54 | 55 | crate::Deadline { 56 | kind: crate::deadline::DeadlineKind::Tokio { t: deadline }, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use async_std::prelude::*; 4 | use stop_token::prelude::*; 5 | 6 | use async_channel::bounded; 7 | use async_std::task; 8 | 9 | use stop_token::StopSource; 10 | 11 | #[test] 12 | fn smoke() { 13 | task::block_on(async { 14 | let (sender, receiver) = bounded::(10); 15 | let source = StopSource::new(); 16 | let task = task::spawn({ 17 | let token = source.token(); 18 | let receiver = receiver.clone(); 19 | async move { 20 | let mut xs = Vec::new(); 21 | let mut stream = receiver.timeout_at(token); 22 | while let Some(Ok(x)) = stream.next().await { 23 | xs.push(x) 24 | } 25 | xs 26 | } 27 | }); 28 | sender.send(1).await.unwrap(); 29 | sender.send(2).await.unwrap(); 30 | sender.send(3).await.unwrap(); 31 | 32 | task::sleep(Duration::from_millis(250)).await; 33 | drop(source); 34 | task::sleep(Duration::from_millis(250)).await; 35 | 36 | sender.send(4).await.unwrap(); 37 | sender.send(5).await.unwrap(); 38 | sender.send(6).await.unwrap(); 39 | assert_eq!(task.await, vec![1, 2, 3]); 40 | }) 41 | } 42 | 43 | #[cfg(feature = "async-io")] 44 | #[test] 45 | fn async_io_time() { 46 | use std::time::Instant; 47 | task::block_on(async { 48 | let (sender, receiver) = bounded::(10); 49 | let task = task::spawn({ 50 | let receiver = receiver.clone(); 51 | async move { 52 | let mut xs = Vec::new(); 53 | let mut stream = receiver.timeout_at(Instant::now() + Duration::from_millis(200)); 54 | while let Some(Ok(x)) = stream.next().await { 55 | xs.push(x) 56 | } 57 | xs 58 | } 59 | }); 60 | sender.send(1).await.unwrap(); 61 | sender.send(2).await.unwrap(); 62 | sender.send(3).await.unwrap(); 63 | 64 | task::sleep(Duration::from_millis(250)).await; 65 | 66 | sender.send(4).await.unwrap(); 67 | sender.send(5).await.unwrap(); 68 | sender.send(6).await.unwrap(); 69 | assert_eq!(task.await, vec![1, 2, 3]); 70 | }) 71 | } 72 | 73 | #[cfg(feature = "tokio")] 74 | #[tokio::test] 75 | async fn tokio_time() { 76 | use tokio::time::Instant; 77 | let (sender, receiver) = bounded::(10); 78 | let task = tokio::task::spawn({ 79 | let receiver = receiver.clone(); 80 | async move { 81 | let mut xs = Vec::new(); 82 | let mut stream = receiver.timeout_at(Instant::now() + Duration::from_millis(200)); 83 | while let Some(Ok(x)) = stream.next().await { 84 | xs.push(x) 85 | } 86 | xs 87 | } 88 | }); 89 | sender.send(1).await.unwrap(); 90 | sender.send(2).await.unwrap(); 91 | sender.send(3).await.unwrap(); 92 | 93 | task::sleep(Duration::from_millis(250)).await; 94 | 95 | sender.send(4).await.unwrap(); 96 | sender.send(5).await.unwrap(); 97 | sender.send(6).await.unwrap(); 98 | assert_eq!(task.await.unwrap(), vec![1, 2, 3]); 99 | } 100 | --------------------------------------------------------------------------------