1use std::{future::Future, time::Duration};
22
23use futures::future::FutureExt;
24use tokio::task::JoinHandle;
25use tokio_util::{sync::CancellationToken, task::TaskTracker};
26use zenoh_core::{ResolveFuture, Wait};
27use zenoh_runtime::ZRuntime;
28
29#[derive(Clone)]
30pub struct TaskController {
31 tracker: TaskTracker,
32 token: CancellationToken,
33}
34
35impl std::fmt::Debug for TaskController {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("TaskController")
38 .field("is_cancelled", &self.token.is_cancelled())
39 .finish_non_exhaustive()
40 }
41}
42
43impl Default for TaskController {
44 fn default() -> Self {
45 TaskController {
46 tracker: TaskTracker::new(),
47 token: CancellationToken::new(),
48 }
49 }
50}
51
52impl TaskController {
53 pub fn into_abortable<'a, F, T>(&self, future: F) -> impl Future<Output = Option<T>> + Send + 'a
55 where
56 F: Future<Output = T> + Send + 'a,
57 T: Send + 'static,
58 {
59 self.token.child_token().run_until_cancelled_owned(future)
60 }
61
62 pub fn spawn_abortable<F, T>(&self, future: F) -> JoinHandle<Option<T>>
65 where
66 F: Future<Output = T> + Send + 'static,
67 T: Send + 'static,
68 {
69 #[cfg(feature = "tracing-instrument")]
70 let future = tracing::Instrument::instrument(future, tracing::Span::current());
71
72 self.tracker.spawn(self.into_abortable(future))
73 }
74
75 pub fn spawn_abortable_with_rt<F, T>(&self, rt: ZRuntime, future: F) -> JoinHandle<Option<T>>
77 where
78 F: Future<Output = T> + Send + 'static,
79 T: Send + 'static,
80 {
81 #[cfg(feature = "tracing-instrument")]
82 let future = tracing::Instrument::instrument(future, tracing::Span::current());
83
84 self.tracker.spawn_on(self.into_abortable(future), &rt)
85 }
86
87 pub fn get_cancellation_token(&self) -> CancellationToken {
88 self.token.child_token()
89 }
90
91 pub fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
96 where
97 F: Future<Output = T> + Send + 'static,
98 T: Send + 'static,
99 {
100 #[cfg(feature = "tracing-instrument")]
101 let future = tracing::Instrument::instrument(future, tracing::Span::current());
102
103 self.tracker.spawn(future)
104 }
105
106 pub fn spawn_with_rt<F, T>(&self, rt: ZRuntime, future: F) -> JoinHandle<T>
111 where
112 F: Future<Output = T> + Send + 'static,
113 T: Send + 'static,
114 {
115 #[cfg(feature = "tracing-instrument")]
116 let future = tracing::Instrument::instrument(future, tracing::Span::current());
117
118 self.tracker.spawn_on(future, &rt)
119 }
120
121 pub fn terminate_all(&self, timeout: Duration) -> usize {
129 ResolveFuture::new(async move {
130 if tokio::time::timeout(timeout, self.terminate_all_async())
131 .await
132 .is_err()
133 {
134 tracing::error!("Failed to terminate {} tasks", self.tracker.len());
135 }
136 self.tracker.len()
137 })
138 .wait()
139 }
140
141 pub async fn terminate_all_async(&self) {
143 self.tracker.close();
144 self.token.cancel();
145 self.tracker.wait().await
146 }
147}
148
149pub struct TerminatableTask {
150 handle: Option<JoinHandle<()>>,
151 token: CancellationToken,
152}
153
154impl std::fmt::Debug for TerminatableTask {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("TerminatableTask")
157 .field("has_handle", &self.handle.is_some())
158 .field("is_cancelled", &self.token.is_cancelled())
159 .finish()
160 }
161}
162
163impl Drop for TerminatableTask {
164 fn drop(&mut self) {
165 self.terminate(std::time::Duration::from_secs(10));
166 }
167}
168
169impl TerminatableTask {
170 pub fn create_cancellation_token() -> CancellationToken {
171 CancellationToken::new()
172 }
173
174 pub fn spawn<F, T>(rt: ZRuntime, future: F, token: CancellationToken) -> TerminatableTask
177 where
178 F: Future<Output = T> + Send + 'static,
179 T: Send + 'static,
180 {
181 TerminatableTask {
182 handle: Some(rt.spawn(future.map(|_f| ()))),
183 token,
184 }
185 }
186
187 pub fn spawn_abortable<F, T>(rt: ZRuntime, future: F) -> TerminatableTask
189 where
190 F: Future<Output = T> + Send + 'static,
191 T: Send + 'static,
192 {
193 let token = CancellationToken::new();
194 let token2 = token.clone();
195 let task = async move {
196 tokio::select! {
197 _ = token2.cancelled() => {},
198 _ = future => {}
199 }
200 };
201
202 TerminatableTask {
203 handle: Some(rt.spawn(task)),
204 token,
205 }
206 }
207
208 pub fn terminate(&mut self, timeout: Duration) -> bool {
211 ResolveFuture::new(async move {
212 if tokio::time::timeout(timeout, self.terminate_async())
213 .await
214 .is_err()
215 {
216 tracing::error!("Failed to terminate the task");
217 return false;
218 };
219 true
220 })
221 .wait()
222 }
223
224 pub async fn terminate_async(&mut self) {
226 self.token.cancel();
227 if let Some(handle) = self.handle.take() {
228 let _ = handle.await;
229 }
230 }
231}