nautilus_common/live/
task.rs1use parking_lot::Mutex;
19
20use super::dst::task::JoinHandle;
21
22#[derive(Debug, Default)]
24pub struct TaskHandles {
25 handles: Mutex<Vec<JoinHandle<()>>>,
26}
27
28impl TaskHandles {
29 pub fn push(&self, handle: JoinHandle<()>) {
31 let mut handles = self.handles.lock();
32 handles.retain(|handle| !handle.is_finished());
33 handles.push(handle);
34 }
35
36 pub fn abort_all(&self) {
38 for handle in self.take_all() {
39 handle.abort();
40 }
41 }
42
43 pub fn abort_all_retained(&self) {
46 for handle in self.handles.lock().iter() {
47 handle.abort();
48 }
49 }
50
51 #[must_use]
53 pub fn take_all(&self) -> Vec<JoinHandle<()>> {
54 let mut handles = self.handles.lock();
55 std::mem::take(&mut *handles)
56 }
57
58 #[must_use]
60 pub fn all_finished(&self) -> bool {
61 self.handles.lock().iter().all(JoinHandle::is_finished)
62 }
63
64 #[must_use]
66 pub fn is_empty(&self) -> bool {
67 self.handles.lock().is_empty()
68 }
69
70 #[must_use]
72 pub fn len(&self) -> usize {
73 self.handles.lock().len()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use std::time::Duration;
80
81 use rstest::rstest;
82
83 use super::*;
84 use crate::live::dst::{task, time};
85
86 #[rstest]
87 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
88 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
89 async fn test_push_prunes_finished_handles() {
90 let tasks = TaskHandles::default();
91
92 let finished = task::spawn(async {});
93
94 time::timeout(Duration::from_secs(1), async {
95 while !finished.is_finished() {
96 task::yield_now().await;
97 }
98 })
99 .await
100 .expect("task should finish");
101
102 tasks.push(finished);
103 tasks.push(task::spawn(std::future::pending()));
104
105 assert_eq!(tasks.len(), 1);
106 tasks.abort_all();
107 }
108
109 #[rstest]
110 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
111 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
112 async fn test_abort_all_drains_before_aborting() {
113 let tasks = TaskHandles::default();
114 let mut drop_receivers = Vec::new();
115
116 for _ in 0..2 {
117 let (drop_tx, drop_rx) = tokio::sync::oneshot::channel();
118 let signal = DropSignal { tx: Some(drop_tx) };
119 tasks.push(task::spawn(async move {
120 let _signal = signal;
121 std::future::pending::<()>().await;
122 }));
123 drop_receivers.push(drop_rx);
124 }
125
126 tasks.abort_all();
127
128 assert!(tasks.is_empty());
129
130 for drop_rx in drop_receivers {
131 time::timeout(Duration::from_secs(1), drop_rx)
132 .await
133 .expect("aborted task should drop its future")
134 .expect("drop signal should be sent");
135 }
136 }
137
138 #[rstest]
139 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
140 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
141 async fn test_abort_all_retained_keeps_finished_handles_observable() {
142 let tasks = TaskHandles::default();
143 let mut drop_receivers = Vec::new();
144
145 for _ in 0..2 {
146 let (drop_tx, drop_rx) = tokio::sync::oneshot::channel();
147 let signal = DropSignal { tx: Some(drop_tx) };
148 tasks.push(task::spawn(async move {
149 let _signal = signal;
150 std::future::pending::<()>().await;
151 }));
152 drop_receivers.push(drop_rx);
153 }
154
155 tasks.abort_all_retained();
156
157 assert!(!tasks.is_empty());
158
159 for drop_rx in drop_receivers {
160 time::timeout(Duration::from_secs(1), drop_rx)
161 .await
162 .expect("aborted task should drop its future")
163 .expect("drop signal should be sent");
164 }
165
166 let _ = time::timeout(Duration::from_secs(1), async {
167 while !tasks.all_finished() {
168 task::yield_now().await;
169 }
170 })
171 .await;
172 assert!(tasks.all_finished());
173 }
174
175 #[rstest]
176 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
177 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
178 async fn test_take_all_extracts_handles() {
179 let tasks = TaskHandles::default();
180 tasks.push(task::spawn(std::future::pending()));
181 tasks.push(task::spawn(std::future::pending()));
182
183 let handles = tasks.take_all();
184
185 assert!(tasks.is_empty());
186 assert_eq!(handles.len(), 2);
187 for handle in handles {
188 handle.abort();
189 }
190 }
191
192 #[rstest]
193 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
194 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
195 async fn test_all_finished_preserves_handles() {
196 let tasks = TaskHandles::default();
197 assert!(tasks.all_finished());
198
199 let (release_tx, release_rx) = tokio::sync::oneshot::channel();
200 tasks.push(task::spawn(async move {
201 let _ = release_rx.await;
202 }));
203
204 assert!(!tasks.all_finished());
205 assert_eq!(tasks.len(), 1);
206
207 release_tx.send(()).expect("task should still be waiting");
208 time::timeout(Duration::from_secs(1), async {
209 while !tasks.all_finished() {
210 task::yield_now().await;
211 }
212 })
213 .await
214 .expect("task should finish");
215
216 assert!(tasks.all_finished());
217 assert_eq!(tasks.len(), 1);
218 }
219
220 #[rstest]
221 #[cfg_attr(not(all(feature = "simulation", madsim)), tokio::test)]
222 #[cfg_attr(all(feature = "simulation", madsim), madsim::test)]
223 async fn test_drop_detaches_tasks() {
224 let tasks = TaskHandles::default();
225 let (release_tx, release_rx) = tokio::sync::oneshot::channel();
226 let (done_tx, done_rx) = tokio::sync::oneshot::channel();
227
228 tasks.push(task::spawn(async move {
229 let _ = release_rx.await;
230 let _ = done_tx.send(());
231 }));
232 drop(tasks);
233 let _ = release_tx.send(());
234
235 time::timeout(Duration::from_secs(1), done_rx)
236 .await
237 .expect("detached task should complete")
238 .expect("completion signal should be sent");
239 }
240
241 struct DropSignal {
242 tx: Option<tokio::sync::oneshot::Sender<()>>,
243 }
244
245 impl Drop for DropSignal {
246 fn drop(&mut self) {
247 if let Some(tx) = self.tx.take() {
248 let _ = tx.send(());
249 }
250 }
251 }
252}