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;