1use std::time::Duration;
11
12use tokio::task::JoinHandle;
13use tokio_util::sync::CancellationToken;
14
15#[derive(Debug)]
23pub struct SpawnedTask {
24 cancel: CancellationToken,
25 handle: JoinHandle<()>,
26}
27
28impl SpawnedTask {
29 pub fn spawn<F, Fut>(body: F) -> Self
33 where
34 F: FnOnce(CancellationToken) -> Fut,
35 Fut: Future<Output = ()> + Send + 'static,
36 {
37 let cancel = CancellationToken::new();
38 let handle = tokio::spawn(body(cancel.clone()));
39 Self { cancel, handle }
40 }
41
42 pub fn spawn_with<Fut>(cancel: CancellationToken, body: Fut) -> Self
44 where
45 Fut: Future<Output = ()> + Send + 'static,
46 {
47 let handle = tokio::spawn(body);
48 Self { cancel, handle }
49 }
50
51 #[must_use]
55 pub const fn from_parts(cancel: CancellationToken, handle: JoinHandle<()>) -> Self {
56 Self { cancel, handle }
57 }
58
59 pub fn cancel(&self) {
61 self.cancel.cancel();
62 }
63
64 #[must_use]
66 pub fn is_finished(&self) -> bool {
67 self.handle.is_finished()
68 }
69
70 #[must_use]
72 pub fn cancellation(&self) -> CancellationToken {
73 self.cancel.clone()
74 }
75
76 pub async fn shutdown(self, grace: Duration) {
79 self.cancel.cancel();
80 let mut handle = self.handle;
81 if tokio::time::timeout(grace, &mut handle).await.is_err() {
82 handle.abort();
83 let _ = handle.await;
84 }
85 }
86
87 pub fn abort(self) {
89 self.handle.abort();
90 }
91
92 pub async fn join(self) {
94 let _ = self.handle.await;
95 }
96}
97
98#[derive(Debug, Default)]
104pub struct TaskGroup {
105 tasks: Vec<SpawnedTask>,
106}
107
108impl TaskGroup {
109 #[must_use]
111 pub const fn new() -> Self {
112 Self { tasks: Vec::new() }
113 }
114
115 pub fn push(&mut self, task: SpawnedTask) {
117 self.tasks.push(task);
118 }
119
120 #[must_use]
122 pub const fn len(&self) -> usize {
123 self.tasks.len()
124 }
125
126 #[must_use]
128 pub const fn is_empty(&self) -> bool {
129 self.tasks.is_empty()
130 }
131
132 pub fn cancel_all(&self) {
134 for task in &self.tasks {
135 task.cancel();
136 }
137 }
138
139 pub async fn shutdown(&mut self, grace: Duration) {
141 let tasks = std::mem::take(&mut self.tasks);
142 for task in &tasks {
143 task.cancel();
144 }
145 for task in tasks {
146 task.shutdown(grace).await;
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::sync::Arc;
155 use std::sync::atomic::{AtomicBool, Ordering};
156
157 #[tokio::test]
158 async fn shutdown_cancels_cooperative_task() {
159 let ran = Arc::new(AtomicBool::new(false));
160 let r = ran.clone();
161 let task = SpawnedTask::spawn(move |cancel| async move {
162 cancel.cancelled().await;
163 r.store(true, Ordering::SeqCst);
164 });
165 task.shutdown(Duration::from_secs(1)).await;
166 assert!(ran.load(Ordering::SeqCst));
167 }
168
169 #[tokio::test(start_paused = true)]
170 async fn shutdown_aborts_wedged_task() {
171 let task = SpawnedTask::spawn(|_cancel| async move {
172 std::future::pending::<()>().await;
173 });
174 task.shutdown(Duration::from_millis(50)).await;
176 }
177
178 #[tokio::test]
179 async fn group_shuts_down_all() {
180 let mut group = TaskGroup::new();
181 for _ in 0..3 {
182 group.push(SpawnedTask::spawn(|cancel| async move {
183 cancel.cancelled().await;
184 }));
185 }
186 assert_eq!(group.len(), 3);
187 group.shutdown(Duration::from_secs(1)).await;
188 assert!(group.is_empty());
189 }
190}