use std::fmt;
use sealed::sealed;
use tokio::task::JoinError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinFailureKind {
Cancelled,
Panicked,
}
impl fmt::Display for JoinFailureKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cancelled => f.write_str("cancelled"),
Self::Panicked => f.write_str("panic"),
}
}
}
#[derive(Debug)]
pub struct JoinTaskError {
task: &'static str,
kind: JoinFailureKind,
payload: Option<String>,
}
impl JoinTaskError {
#[must_use]
pub fn task(&self) -> &'static str {
self.task
}
#[must_use]
pub const fn kind(&self) -> JoinFailureKind {
self.kind
}
#[must_use]
pub fn payload(&self) -> Option<&str> {
self.payload.as_deref()
}
}
impl fmt::Display for JoinTaskError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
JoinFailureKind::Cancelled => write!(f, "task `{}` cancelled", self.task),
JoinFailureKind::Panicked => {
write!(f, "task `{}` panicked: ", self.task)?;
f.write_str(self.payload.as_deref().unwrap_or("unknown panic payload"))
}
}
}
}
impl std::error::Error for JoinTaskError {}
fn panic_payload(e: JoinError) -> Option<String> {
let payload = e.into_panic();
crate::exn::payload_str(&payload).map(str::to_owned)
}
#[sealed]
pub trait ObserveJoinExt<T> {
#[track_caller]
fn observe_join(self, task: &'static str) -> crate::Result<T, JoinTaskError>;
}
impl<T> __seal_observe_join_ext::Sealed<T> for Result<T, JoinError> {}
impl<T> ObserveJoinExt<T> for Result<T, JoinError> {
#[track_caller]
#[cold]
fn observe_join(self, task: &'static str) -> crate::Result<T, JoinTaskError> {
match self {
Ok(v) => Ok(v),
Err(e) => {
let (kind, payload) = if e.is_cancelled() {
(JoinFailureKind::Cancelled, None)
} else if e.is_panic() {
(JoinFailureKind::Panicked, panic_payload(e))
} else {
(JoinFailureKind::Panicked, Some(e.to_string()))
};
let kind_str = kind.to_string();
Err(crate::Fault::new(JoinTaskError {
task,
kind,
payload,
})
.attach_key("task", task)
.attach_key("join", kind_str))
}
}
}
}