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