set_timeout/
lib.rs

1//! This crate allows executing futures after a certain delay, similar to how `setTimeout` works in
2//! js. The scheduling of a future can be cancelled as long as the future wasn't already executed,
3//! by using a `CancellationToken`.
4//!
5//! This crate uses a scheduler which allows running an unlimited number of delayed futures using
6//! just a single `tokio` task, in contrast to many crates which spawn a new task for each set
7//! timeout call. This uses less memory and should be more performant.
8//!
9//! Please note that this crate can only be used in the context of a tokio runtime.
10//!
11//! # Example
12//!
13//! ```rust
14//! #[tokio::main]
15//! async fn main() {
16//!     let scheduler = TimeoutScheduler::new(None);
17//!
18//!     let start = Instant::now();
19//!
20//!     // schedule a future which will run after at least 1.234 seconds from now.
21//!     scheduler.set_timeout(Duration::from_secs_f32(1.234), async move {
22//!         let elapsed = start.elapsed();
23//!
24//!         assert!(elapsed.as_secs_f32() > 1.234);
25//!
26//!         println!("elapsed: {:?}", elapsed);
27//!     });
28//!
29//!     // make sure that the main task doesn't end before the timeout is executed, because if the main
30//!     // task returns the runtime stops running.
31//!     tokio::time::sleep(Duration::from_secs(2)).await;
32//! }
33//! ```
34//!
35//! ## Usage Tip
36//! You can schedule many timeouts on the scheduler, but you should avoid scheduling futures
37//! which take a long time to execute, since such futures can block the scheduler from executing
38//! other scheduled timeouts, and may cause other timeouts to execute at a big delay.
39//!
40//! If you really need to schedule some future which takes a long time, consider scheduling a 
41//! future which spawns a new task and then does all the heavy stuff.
42//!
43//! ## Sharing The Scheduler
44//! The timeout scheduler can be shared between multiple tasks, by storing it in an [`Arc`], or by
45//! storing it in a global variable using the `lazy_static` crate. For an example of this, check
46//! out the example called `global_variable` and the example called `multitasking` in the examples directory.
47//!
48//! [`Arc`]: std::sync::Arc
49
50#![feature(map_first_last)]
51
52use futures::future::BoxFuture;
53use scheduler_task::{SchedulerTask, SchedulerTaskHandler};
54use std::{
55    future::Future,
56    ops::Deref,
57    time::{Duration, Instant},
58};
59
60pub use scheduler_task::CancellationToken;
61
62mod scheduled_timeout;
63mod scheduler_task;
64
65/// A timeout scheduler, used for running futures at some delay.
66///
67/// This scheduler will use a single `tokio` task no matter how many timeouts are scheduled.
68///
69/// The scheduler may be wrapped in an [`Arc`] or stored in a global variable using `lazy_static` 
70/// in order to share it across multiple tasks.
71///
72/// [`Arc`]: std::sync::Arc
73#[derive(Debug)]
74pub struct TimeoutScheduler {
75    scheduler_task_handler: SchedulerTaskHandler,
76}
77
78impl TimeoutScheduler {
79    /// Creates a new timeout scheduler.
80    ///
81    /// The `min_sleep_duration` parameter is the minimum sleep duration that the scheduler will
82    /// sleep for. Any timeout which requires sleeping for a delay smaller than this will be executed immediately.
83    ///
84    /// Note that a duration value greater than 0 for `min_sleep_duration` means that the scheduled
85    /// timeouts **may** execute earlier than their delay. They will be early by **at most** the duration
86    /// of `min_sleep_duration`. This may slightly increase the performance because it avoids
87    /// unnecessary short sleeps.
88    ///
89    /// A value of `None` for `min_sleep_duration` means that there is no minimum sleep duration,
90    /// which means that the scheduler will wait for every timeout however small it is. This
91    /// guarantees that the timeouts will never run before their delay has exceeded.
92    pub fn new(min_sleep_duration: Option<Duration>) -> Self {
93        Self {
94            scheduler_task_handler: SchedulerTask::run(
95                min_sleep_duration.unwrap_or_else(|| Duration::from_secs(0)),
96            ),
97        }
98    }
99
100    /// Executes the given future after the given delay has exceeded.
101    ///
102    /// The delay at which the future is actually executed might be bigger than the given `delay`,
103    /// and if `min_sleep_duration` was set, the delay might even be smaller than the given `delay`.
104    ///
105    /// This function returns a [`CancellationToken`], which allows cancelling this timeout using a
106    /// call to [`TimeoutScheduler::cancel_timeout`]
107    pub fn set_timeout<Fut>(&self, delay: Duration, f: Fut) -> CancellationToken
108    where
109        Fut: Future<Output = ()> + Send + 'static,
110    {
111        let boxed_future = Box::pin(f);
112        self.scheduler_task_handler
113            .schedule_timeout(Instant::now() + delay, boxed_future)
114    }
115
116    /// Cancels the timeout associated with the given cancellation token.
117    /// If the timeout was already executed or already cancelled, this does nothing.
118    pub fn cancel_timeout(&self, cancellation_token: CancellationToken) {
119        self.scheduler_task_handler
120            .cancel_timeout(cancellation_token)
121    }
122}
123
124/// Returns the address of this boxed future in memory.
125fn addr_of_boxed_future<'a, T>(boxed_future: &BoxFuture<'a, T>) -> usize {
126    boxed_future.deref() as *const (dyn Future<Output = T> + Send) as *const () as usize
127}