use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use futures::FutureExt;
use crate::critical::CriticalFailureKind;
use crate::critical::CriticalRegistration;
use super::TaskCompletion;
use super::TaskGroupInner;
use super::TaskId;
use super::TaskResult;
pub(super) struct TaskExecution<F> {
future: F,
finalizer: TaskFinalizer,
propagate_panic: bool,
}
impl<F: Future<Output = ()>> TaskExecution<F> {
pub(super) fn new(
future: F,
inner: Arc<TaskGroupInner>,
task_id: TaskId,
completion: Arc<TaskCompletion>,
propagate_panic: bool,
critical: Option<CriticalRegistration>,
) -> Self {
Self {
future,
finalizer: TaskFinalizer {
inner,
task_id,
completion,
result: TaskResult::Aborted,
critical,
},
propagate_panic,
}
}
pub(super) async fn run(self) {
let mut finalizer = self.finalizer;
let result = AssertUnwindSafe(self.future).catch_unwind().await;
match result {
Ok(()) => {
if finalizer.inner.cancellation_token.is_cancelled() {
finalizer.result = TaskResult::Cancelled;
} else {
finalizer.result = TaskResult::Completed;
}
}
Err(error) => {
finalizer.result = TaskResult::Panicked;
tracing::error!(task_id = finalizer.task_id.as_u64(), "task panicked");
if self.propagate_panic {
std::panic::resume_unwind(error);
}
}
}
}
}
struct TaskFinalizer {
inner: Arc<TaskGroupInner>,
task_id: TaskId,
completion: Arc<TaskCompletion>,
result: TaskResult,
critical: Option<CriticalRegistration>,
}
impl Drop for TaskFinalizer {
fn drop(&mut self) {
let result = if std::thread::panicking() {
TaskResult::Panicked
} else {
self.result
};
if let Some(critical) = &self.critical {
let failure = match result {
TaskResult::Panicked => Some(CriticalFailureKind::Panicked),
TaskResult::Completed if critical.expects_until_cancelled => {
Some(CriticalFailureKind::ExitedUnexpectedly)
}
TaskResult::Completed | TaskResult::Cancelled | TaskResult::Aborted => None,
};
if let Some(kind) = failure {
critical.failures.record(kind, critical.task_kind);
}
}
self.inner.finish_task(self.task_id, result);
self.completion.mark_done();
}
}