zebra_chain/diagnostic/task/future.rs
1//! Diagnostic types and functions for Zebra async future tasks:
2//! - task handles
3//! - errors and panics
4
5use std::{future, panic};
6
7use futures::future::{BoxFuture, FutureExt};
8use tokio::task::{JoinError, JoinHandle};
9
10use crate::shutdown::is_shutting_down;
11
12use super::{CheckForPanics, WaitForPanics};
13
14/// This is the return type of the [`JoinHandle`] future.
15impl<T> CheckForPanics for Result<T, JoinError> {
16 /// The [`JoinHandle`]'s task output, after resuming any panics,
17 /// and ignoring task cancellations on shutdown.
18 type Output = Result<T, JoinError>;
19
20 /// Returns the task result if the task finished normally.
21 /// Otherwise, resumes any panics, and ignores any expected errors.
22 /// Handles unexpected errors based on `panic_on_unexpected_termination`.
23 ///
24 /// If the task finished normally, returns `Some(T)`.
25 /// If the task was cancelled, returns `None`.
26 #[track_caller]
27 fn check_for_panics_with(self, panic_on_unexpected_termination: bool) -> Self::Output {
28 match self {
29 Ok(task_output) => Ok(task_output),
30 Err(join_error) => {
31 Err(join_error.check_for_panics_with(panic_on_unexpected_termination))
32 }
33 }
34 }
35}
36
37impl CheckForPanics for JoinError {
38 /// The [`JoinError`] after resuming any panics, and logging any unexpected task cancellations.
39 type Output = JoinError;
40
41 /// Resume any panics and panic on unexpected task cancellations.
42 /// Always returns [`JoinError::Cancelled`](JoinError::is_cancelled).
43 #[track_caller]
44 fn check_for_panics_with(self, panic_on_unexpected_termination: bool) -> Self::Output {
45 match self.try_into_panic() {
46 Ok(panic_payload) => panic::resume_unwind(panic_payload),
47
48 // We could ignore this error, but then we'd have to change the return type.
49 Err(task_cancelled) => {
50 if !panic_on_unexpected_termination {
51 debug!(?task_cancelled, "ignoring expected task termination");
52
53 task_cancelled
54 } else if is_shutting_down() {
55 debug!(
56 ?task_cancelled,
57 "ignoring task termination because Zebra is shutting down"
58 );
59
60 task_cancelled
61 } else {
62 panic!("task unexpectedly exited with: {:?}", task_cancelled)
63 }
64 }
65 }
66 }
67}
68
69impl<T> WaitForPanics for JoinHandle<T>
70where
71 T: Send + 'static,
72{
73 type Output = BoxFuture<'static, T>;
74
75 /// Returns a future which waits for `self` to finish, then checks if its output is:
76 /// - a panic payload: resume that panic,
77 /// - an unexpected termination:
78 /// - if `panic_on_unexpected_termination` is true, panic with that error,
79 /// - otherwise, hang waiting for shutdown,
80 /// - an expected termination: hang waiting for shutdown.
81 ///
82 /// Otherwise, returns the task return value of `self`.
83 ///
84 /// # Panics
85 ///
86 /// If `self` contains a panic payload, or [`JoinHandle::abort()`] has been called on `self`.
87 ///
88 /// # Hangs
89 ///
90 /// If `self` contains an expected termination, and we're shutting down anyway.
91 /// If we're ignoring terminations because `panic_on_unexpected_termination` is `false`.
92 /// Futures hang by returning `Pending` and not setting a waker, so this uses minimal resources.
93 #[track_caller]
94 fn wait_for_panics_with(self, panic_on_unexpected_termination: bool) -> Self::Output {
95 async move {
96 match self
97 .await
98 .check_for_panics_with(panic_on_unexpected_termination)
99 {
100 Ok(task_output) => task_output,
101 Err(_expected_cancel_error) => future::pending().await,
102 }
103 }
104 .boxed()
105 }
106}