tokio_context/
lib.rs

1//! Provides two different methods for cancelling futures with a provided handle for cancelling all
2//! related futures, with a fallback timeout mechanism. This is accomplished either with the
3//! [`Context`] API, or with the [`TaskController`] API depending on a users needs.
4//!
5//! ## Context
6//!
7//! Provides Golang like Context functionality. A Context in this respect is an object that is
8//! passed around, primarily to async functions, that is used to determine if long running
9//! asynchronous tasks should continue to run, or terminate.
10//!
11//! You build a new Context by calling its [`new`](fn@context::Context::new)
12//! constructor, which returns the new [`Context`] along with a [`Handle`]. The [`Handle`] can
13//! either have its `cancel` method called, or it can simply be dropped to cancel the context.
14//!
15//! Please note that dropping the [`Handle`] **will** cancel the context.
16//!
17//! If you would like to create a Context that automatically cancels after a given duration has
18//! passed, use the [`with_timeout`](fn@context::Context::with_timeout) constructor. Using this
19//! constructor will still give you a handle that can be used to immediately cancel the context as
20//! well.
21//!
22//! # Examples
23//!
24//! ```rust
25//! use tokio::time;
26//! use tokio_context::context::Context;
27//! use std::time::Duration;
28//!
29//! async fn task_that_takes_too_long() {
30//!     time::sleep(time::Duration::from_secs(60)).await;
31//!     println!("done");
32//! }
33//!
34//! #[tokio::main]
35//! async fn main() {
36//!     // We've decided that we want a long running asynchronous task to last for a maximum of 1
37//!     // second.
38//!     let (mut ctx, _handle) = Context::with_timeout(Duration::from_secs(1));
39//!     
40//!     tokio::select! {
41//!         _ = ctx.done() => return,
42//!         _ = task_that_takes_too_long() => panic!("should never have gotten here"),
43//!     }
44//! }
45//!
46//! ```
47//!
48//! While this may look no different than simply using [`tokio::time::timeout`], we have retained a
49//! handle that we can use to explicitly cancel the context, and any additionally spawned
50//! contexts.
51//!
52//!
53//! ```rust
54//! use std::time::Duration;
55//! use tokio::time;
56//! use tokio::task;
57//! use tokio_context::context::Context;
58//!
59//! async fn task_that_takes_too_long(mut ctx: Context) {
60//!     tokio::select! {
61//!         _ = ctx.done() => println!("cancelled early due to context"),
62//!         _ = time::sleep(time::Duration::from_secs(60)) => println!("done"),
63//!     }
64//! }
65//!
66//! #[tokio::main]
67//! async fn main() {
68//!     let (_, mut handle) = Context::new();
69//!
70//!     let mut join_handles = vec![];
71//!
72//!     for i in 0..10 {
73//!         let mut ctx = handle.spawn_ctx();
74//!         let handle = task::spawn(async { task_that_takes_too_long(ctx).await });
75//!         join_handles.push(handle);
76//!     }
77//!
78//!     // Will cancel all spawned contexts.
79//!     handle.cancel();
80//!
81//!     // Now all join handles should gracefully close.
82//!     for join in join_handles {
83//!         join.await.unwrap();
84//!     }
85//! }
86//!
87//! ```
88//!
89//! Contexts may also be chained by using the `with_parent` constructor in conjunction with
90//! RefContexts. Chaining a context means that the context will be cancelled if a parent context is
91//! cancelled. A [`RefContext`] is simple a wrapper type around an `Arc<Mutex<Context>>` with an
92//! identical API to [`Context`]. Here are a few examples to demonstrate how chainable contexts work:
93//!
94//! ```rust
95//! use std::time::Duration;
96//! use tokio::time;
97//! use tokio::task;
98//! use tokio_context::context::RefContext;
99//!
100//! #[tokio::test]
101//! async fn cancelling_parent_ctx_cancels_child() {
102//!     // Note that we can't simply drop the handle here or the context will be cancelled.
103//!     let (parent_ctx, parent_handle) = RefContext::new();
104//!     let (mut ctx, _handle) = Context::with_parent(&parent_ctx, None);
105//!
106//!     parent_handle.cancel();
107//!
108//!     // Cancelling a parent will cancel the child context.
109//!     tokio::select! {
110//!         _ = ctx.done() => assert!(true),
111//!         _ = tokio::time::sleep(Duration::from_millis(15)) => assert!(false),
112//!     }
113//! }
114//!
115//! #[tokio::test]
116//! async fn cancelling_child_ctx_doesnt_cancel_parent() {
117//!     // Note that we can't simply drop the handle here or the context will be cancelled.
118//!     let (mut parent_ctx, _parent_handle) = RefContext::new();
119//!     let (_ctx, handle) = Context::with_parent(&parent_ctx, None);
120//!
121//!     handle.cancel();
122//!
123//!     // Cancelling a child will not cancel the parent context.
124//!     tokio::select! {
125//!         _ = parent_ctx.done() => assert!(false),
126//!         _ = async {} => assert!(true),
127//!     }
128//! }
129//!
130//! #[tokio::test]
131//! async fn parent_timeout_cancels_child() {
132//!     // Note that we can't simply drop the handle here or the context will be cancelled.
133//!     let (parent_ctx, _parent_handle) = RefContext::with_timeout(Duration::from_millis(5));
134//!     let (mut ctx, _handle) =
135//!         Context::with_parent(&parent_ctx, Some(Duration::from_millis(10)));
136//!
137//!     tokio::select! {
138//!         _ = ctx.done() => assert!(true),
139//!         _ = tokio::time::sleep(Duration::from_millis(7)) => assert!(false),
140//!     }
141//! }
142//! ```
143//!
144//! The Context pattern is useful if your child future needs to know about the cancel signal. This
145//! is highly useful in many situations where a child future needs to perform graceful termination.
146//!
147//! In instances where graceful termination of child futures is not needed, the API provided by
148//! [`TaskController`] is much nicer to use. It doesn't pollute children with
149//! an extra function argument of the context. It will however perform abrupt future termination,
150//! which may not always be desired.
151//!
152//! ## TaskController
153//!
154//! Handles spawning tasks which can also be cancelled by calling `cancel` on the task controller.
155//! If a [`std::time::Duration`] is supplied using the
156//! [`with_timeout`](fn@task::TaskController::with_timeout) constructor, then any tasks spawned by
157//! the TaskController will automatically be cancelled after the supplied duration has elapsed.
158//!
159//! This provides a different API from Context for the same end result. It's nicer to use when you
160//! don't need child futures to gracefully shutdown. In cases that you do require graceful shutdown
161//! of child futures, you will need to pass a Context down, and incorporate the context into normal
162//! program flow for the child function so that they can react to it as needed and perform custom
163//! asynchronous cleanup logic.
164//!
165//! # Examples
166//!
167//! ```rust
168//! use std::time::Duration;
169//! use tokio::time;
170//! use tokio_context::task::TaskController;
171//!
172//! async fn task_that_takes_too_long() {
173//!     time::sleep(time::Duration::from_secs(60)).await;
174//!     println!("done");
175//! }
176//!
177//! #[tokio::main]
178//! async fn main() {
179//!     let mut controller = TaskController::new();
180//!
181//!     let mut join_handles = vec![];
182//!
183//!     for i in 0..10 {
184//!         let handle = controller.spawn(async { task_that_takes_too_long().await });
185//!         join_handles.push(handle);
186//!     }
187//!
188//!     // Will cancel all spawned contexts.
189//!     controller.cancel();
190//!
191//!     // Now all join handles should gracefully close.
192//!     for join in join_handles {
193//!         join.await.unwrap();
194//!     }
195//! }
196//! ```
197//!
198//! [`Context`]: context::Context
199//! [`Handle`]: context::Handle
200//! [`RefContext`]: context::RefContext
201//! [`TaskController`]: task::TaskController
202//! [`Duration`]: std::time::Duration
203
204/// Contains the Context API, providing a form of a cancellation token to child processes, with the
205/// option of falling back to a timeout cancel.
206pub mod context;
207/// Contains the TaskController API, providing a way to spawn tasks that can be can cancelled using
208/// the TaskController, with the option of falling back to a timeout cancel.
209pub mod task;