1#![cfg_attr(target_family = "wasm", allow(dead_code))]
2
3mod inner;
4
5pub mod jit;
7pub mod waiter;
8
9use std::future::Future;
10use std::pin::{Pin, pin};
11use std::sync::Arc;
12use std::time::SystemTime;
13
14use fedimint_core::time::now;
15use fedimint_logging::{LOG_TASK, LOG_TEST};
16use futures::future::{self, Either};
17use inner::TaskGroupInner;
18use scopeguard::defer;
19use thiserror::Error;
20use tokio::sync::{oneshot, watch};
21use tracing::{Span, debug, info, trace};
22
23use crate::runtime;
24pub use crate::runtime::*;
27#[derive(Clone, Default, Debug)]
38pub struct TaskGroup {
39 inner: Arc<TaskGroupInner>,
40}
41
42impl TaskGroup {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn make_handle(&self) -> TaskHandle {
48 TaskHandle {
49 inner: self.inner.clone(),
50 }
51 }
52
53 pub fn make_subgroup(&self) -> Self {
66 let new_tg = Self::new();
67 self.inner.add_subgroup(new_tg.clone());
68 new_tg
69 }
70
71 pub fn is_shutting_down(&self) -> bool {
73 self.inner.is_shutting_down()
74 }
75
76 pub fn shutdown(&self) {
79 self.inner.shutdown();
80 }
81
82 pub async fn shutdown_join_all(
84 self,
85 join_timeout: impl Into<Option<Duration>>,
86 ) -> Result<(), JoinAllError> {
87 self.shutdown();
88 self.join_all(join_timeout.into()).await
89 }
90
91 #[cfg(not(target_family = "wasm"))]
94 pub fn install_kill_handler(&self) {
95 async fn wait_for_shutdown_signal() {
97 use tokio::signal;
98
99 let ctrl_c = async {
100 signal::ctrl_c()
101 .await
102 .expect("failed to install Ctrl+C handler");
103 };
104
105 #[cfg(unix)]
106 let terminate = async {
107 signal::unix::signal(signal::unix::SignalKind::terminate())
108 .expect("failed to install signal handler")
109 .recv()
110 .await;
111 };
112
113 #[cfg(not(unix))]
114 let terminate = std::future::pending::<()>();
115
116 tokio::select! {
117 () = ctrl_c => {},
118 () = terminate => {},
119 }
120 }
121
122 runtime::spawn("kill handlers", {
123 let task_group = self.clone();
124 async move {
125 wait_for_shutdown_signal().await;
126 info!(
127 target: LOG_TASK,
128 "signal received, starting graceful shutdown"
129 );
130 task_group.shutdown();
131 }
132 });
133 }
134
135 pub fn spawn<Fut, R>(
136 &self,
137 name: impl Into<String>,
138 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
139 ) -> oneshot::Receiver<R>
140 where
141 Fut: Future<Output = R> + MaybeSend + 'static,
142 R: MaybeSend + 'static,
143 {
144 self.spawn_inner(name, f, false, None)
145 }
146
147 pub fn spawn_silent<Fut, R>(
151 &self,
152 name: impl Into<String>,
153 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
154 ) -> oneshot::Receiver<R>
155 where
156 Fut: Future<Output = R> + MaybeSend + 'static,
157 R: MaybeSend + 'static,
158 {
159 self.spawn_inner(name, f, true, None)
160 }
161
162 pub fn spawn_with_span<Fut, R>(
166 &self,
167 parent_span: Span,
168 name: impl Into<String>,
169 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
170 ) -> oneshot::Receiver<R>
171 where
172 Fut: Future<Output = R> + MaybeSend + 'static,
173 R: MaybeSend + 'static,
174 {
175 self.spawn_inner(name, f, false, Some(parent_span))
176 }
177
178 fn spawn_inner<Fut, R>(
179 &self,
180 name: impl Into<String>,
181 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
182 quiet: bool,
183 parent_span: Option<Span>,
184 ) -> oneshot::Receiver<R>
185 where
186 Fut: Future<Output = R> + MaybeSend + 'static,
187 R: MaybeSend + 'static,
188 {
189 let name = name.into();
190 let mut guard = TaskPanicGuard {
191 name: name.clone(),
192 inner: self.inner.clone(),
193 completed: false,
194 };
195 let handle = self.make_handle();
196
197 let (tx, rx) = oneshot::channel();
198 self.inner
199 .active_tasks_join_handles
200 .lock()
201 .expect("Locking failed")
202 .insert_with_key(move |task_key| {
203 let task_future = {
204 let name = name.clone();
205 async move {
206 defer! {
207 if handle
212 .inner
213 .active_tasks_join_handles
214 .lock()
215 .expect("Locking failed")
216 .remove(task_key)
217 .is_none() {
218 trace!(target: LOG_TASK, %name, "Task already canceled");
219 }
220 }
221 if quiet {
223 trace!(target: LOG_TASK, %name, "Starting task");
224 } else {
225 debug!(target: LOG_TASK, %name, "Starting task");
226 }
227 let r = f(handle.clone()).await;
228 guard.completed = true;
229
230 if quiet {
231 trace!(target: LOG_TASK, %name, "Finished task");
232 } else {
233 debug!(target: LOG_TASK, %name, "Finished task");
234 }
235 let _ = tx.send(r);
237
238 drop(guard);
241 }
242 };
243 let join_handle = match parent_span.as_ref() {
244 Some(parent) => crate::runtime::spawn_with_span(parent, &name, task_future),
245 None => crate::runtime::spawn(&name, task_future),
246 };
247 (name, join_handle)
248 });
249
250 rx
251 }
252
253 pub fn spawn_cancellable<R>(
256 &self,
257 name: impl Into<String>,
258 future: impl Future<Output = R> + MaybeSend + 'static,
259 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
260 where
261 R: MaybeSend + 'static,
262 {
263 self.spawn(name, |handle| async move {
264 let value = handle.cancel_on_shutdown(future).await;
265 if value.is_err() {
266 debug!(target: LOG_TASK, "task cancelled on shutdown");
268 }
269 value
270 })
271 }
272
273 pub fn spawn_cancellable_with_span<R>(
276 &self,
277 parent_span: Span,
278 name: impl Into<String>,
279 future: impl Future<Output = R> + MaybeSend + 'static,
280 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
281 where
282 R: MaybeSend + 'static,
283 {
284 self.spawn_with_span(parent_span, name, |handle| async move {
285 let value = handle.cancel_on_shutdown(future).await;
286 if value.is_err() {
287 debug!(target: LOG_TASK, "task cancelled on shutdown");
289 }
290 value
291 })
292 }
293
294 pub fn spawn_cancellable_silent<R>(
295 &self,
296 name: impl Into<String>,
297 future: impl Future<Output = R> + MaybeSend + 'static,
298 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
299 where
300 R: MaybeSend + 'static,
301 {
302 self.spawn_silent(name, |handle| async move {
303 let value = handle.cancel_on_shutdown(future).await;
304 if value.is_err() {
305 debug!(target: LOG_TASK, "task cancelled on shutdown");
307 }
308 value
309 })
310 }
311
312 pub async fn join_all(self, timeout: Option<Duration>) -> Result<(), JoinAllError> {
313 let deadline = timeout.map(|timeout| now() + timeout);
314 let mut errors = vec![];
315
316 self.join_all_inner(deadline, &mut errors).await;
317
318 if errors.is_empty() {
319 Ok(())
320 } else {
321 Err(JoinAllError { errors })
322 }
323 }
324
325 #[cfg_attr(not(target_family = "wasm"), ::async_recursion::async_recursion)]
326 #[cfg_attr(target_family = "wasm", ::async_recursion::async_recursion(?Send))]
327 pub async fn join_all_inner(self, deadline: Option<SystemTime>, errors: &mut Vec<JoinError>) {
328 self.inner.join_all(deadline, errors).await;
329 }
330}
331
332struct TaskPanicGuard {
333 name: String,
334 inner: Arc<TaskGroupInner>,
335 completed: bool,
337}
338
339impl Drop for TaskPanicGuard {
340 fn drop(&mut self) {
341 trace!(
342 target: LOG_TASK,
343 name = %self.name,
344 "Task drop"
345 );
346 if !self.completed {
347 info!(
348 target: LOG_TASK,
349 name = %self.name,
350 "Task shut down uncleanly"
351 );
352 self.inner.shutdown();
353 }
354 }
355}
356
357#[derive(Clone, Debug)]
358pub struct TaskHandle {
359 inner: Arc<TaskGroupInner>,
360}
361
362#[derive(thiserror::Error, Debug, Clone)]
363#[error("Task group is shutting down")]
364#[non_exhaustive]
365pub struct ShuttingDownError {}
366
367#[derive(Debug, Error)]
369#[error("{} tasks did not finish cleanly: {errors:?}", .errors.len())]
370#[non_exhaustive]
371pub struct JoinAllError {
372 pub errors: Vec<JoinError>,
376}
377
378impl TaskHandle {
379 pub fn is_shutting_down(&self) -> bool {
383 self.inner.is_shutting_down()
384 }
385
386 pub fn make_shutdown_rx(&self) -> TaskShutdownToken {
391 self.inner.make_shutdown_rx()
392 }
393
394 pub async fn cancel_on_shutdown<F: Future>(
396 &self,
397 fut: F,
398 ) -> Result<F::Output, ShuttingDownError> {
399 let rx = self.make_shutdown_rx();
400 match future::select(pin!(rx), pin!(fut)).await {
401 Either::Left(((), _)) => Err(ShuttingDownError {}),
402 Either::Right((value, _)) => Ok(value),
403 }
404 }
405}
406
407pub struct TaskShutdownToken(Pin<Box<dyn Future<Output = ()> + Send>>);
408
409impl TaskShutdownToken {
410 fn new(mut rx: watch::Receiver<bool>) -> Self {
411 Self(Box::pin(async move {
412 let _ = rx.wait_for(|v| *v).await;
413 }))
414 }
415}
416
417impl Future for TaskShutdownToken {
418 type Output = ();
419
420 fn poll(
421 mut self: Pin<&mut Self>,
422 cx: &mut std::task::Context<'_>,
423 ) -> std::task::Poll<Self::Output> {
424 self.0.as_mut().poll(cx)
425 }
426}
427
428#[macro_export]
445macro_rules! async_trait_maybe_send {
446 ($($tt:tt)*) => {
447 #[cfg_attr(not(target_family = "wasm"), ::async_trait::async_trait)]
448 #[cfg_attr(target_family = "wasm", ::async_trait::async_trait(?Send))]
449 $($tt)*
450 };
451}
452
453#[cfg(not(target_family = "wasm"))]
464#[macro_export]
465macro_rules! maybe_add_send {
466 ($($tt:tt)*) => {
467 $($tt)* + Send
468 };
469}
470
471#[cfg(target_family = "wasm")]
479#[macro_export]
480macro_rules! maybe_add_send {
481 ($($tt:tt)*) => {
482 $($tt)*
483 };
484}
485
486#[cfg(not(target_family = "wasm"))]
488#[macro_export]
489macro_rules! maybe_add_send_sync {
490 ($($tt:tt)*) => {
491 $($tt)* + Send + Sync
492 };
493}
494
495#[cfg(target_family = "wasm")]
497#[macro_export]
498macro_rules! maybe_add_send_sync {
499 ($($tt:tt)*) => {
500 $($tt)*
501 };
502}
503
504#[cfg(target_family = "wasm")]
509pub trait MaybeSend {}
510
511#[cfg(not(target_family = "wasm"))]
516pub trait MaybeSend: Send {}
517
518#[cfg(not(target_family = "wasm"))]
519impl<T: Send> MaybeSend for T {}
520
521#[cfg(target_family = "wasm")]
522impl<T> MaybeSend for T {}
523
524#[cfg(target_family = "wasm")]
526pub trait MaybeSync {}
527
528#[cfg(not(target_family = "wasm"))]
530pub trait MaybeSync: Sync {}
531
532#[cfg(not(target_family = "wasm"))]
533impl<T: Sync> MaybeSync for T {}
534
535#[cfg(target_family = "wasm")]
536impl<T> MaybeSync for T {}
537
538pub async fn sleep_in_test(comment: impl AsRef<str>, duration: Duration) {
541 info!(
542 target: LOG_TEST,
543 "Sleeping for {}.{:03} seconds because: {}",
544 duration.as_secs(),
545 duration.subsec_millis(),
546 comment.as_ref()
547 );
548 sleep(duration).await;
549}
550
551#[derive(Error, Debug)]
553#[error("Operation cancelled")]
554pub struct Cancelled;
555
556pub type Cancellable<T> = std::result::Result<T, Cancelled>;
559
560#[cfg(test)]
561mod tests;