use std::sync::Mutex;
#[derive(Debug, thiserror::Error)]
pub enum JobHandleError {
#[error("the job was cancelled")]
JobCancelled,
#[error("the job panicked during processing: {0:?}")]
JobPanicked(PanicInfo),
#[error("channel send error cancelling job")]
CancelJobError(#[from] tokio::sync::watch::error::SendError<()>),
#[error("channel recv error waiting for job results: {0}")]
JobResultError(#[from] tokio::sync::oneshot::error::RecvError),
#[error("downcast failed converting '{from}' to '{to}'")]
JobResultWrapperError {
from: &'static str,
to: &'static str,
},
}
impl From<Box<dyn std::any::Any + Send + 'static>> for JobHandleError {
fn from(panic_info: Box<dyn std::any::Any + Send + 'static>) -> Self {
Self::JobPanicked(PanicInfo(Mutex::new(panic_info)))
}
}
impl JobHandleError {
pub fn is_panic(&self) -> bool {
matches!(self, Self::JobPanicked(_))
}
pub fn into_panic(self) -> Box<dyn std::any::Any + Send + 'static> {
self.try_into_panic()
.expect("should be JobPanicked variant")
}
pub fn try_into_panic(self) -> Result<Box<dyn std::any::Any + Send + 'static>, Self> {
match self {
Self::JobPanicked(panic_mutex) => {
Ok(panic_mutex.0.into_inner().unwrap())
}
other => Err(other), }
}
pub fn panic_message(&self) -> Option<String> {
match self {
JobHandleError::JobPanicked(panic_mutex) => {
let guard = panic_mutex.0.lock().unwrap();
if let Some(s) = guard.downcast_ref::<&'static str>() {
Some((*s).to_string())
} else {
guard.downcast_ref::<String>().cloned()
}
}
_ => None,
}
}
}
pub struct PanicInfo(Mutex<Box<dyn std::any::Any + Send + 'static>>);
impl std::fmt::Debug for PanicInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = self.0.try_lock().ok().and_then(|guard| {
guard
.downcast_ref::<&'static str>()
.map(|s| (*s).to_string())
.or_else(|| guard.downcast_ref::<String>().cloned())
});
match message {
Some(message) => write!(f, "PanicInfo({message:?})"),
None => write!(f, "PanicInfo(<non-string panic payload>)"),
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum AddJobError {
#[error("channel send error adding job. error: {0}")]
SendError(#[from] tokio::sync::mpsc::error::SendError<()>),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StopQueueError {
#[error("channel send error adding job. error: {0}")]
SendError(#[from] tokio::sync::watch::error::SendError<()>),
#[error("join error while waiting for job-queue to stop. error: {0}")]
JoinError(#[from] tokio::task::JoinError),
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn job_handle_error_into_panic() {
let join_err = tokio::spawn(async { panic!("boom") }).await.unwrap_err();
let err: JobHandleError = join_err.into_panic().into();
let _ = err.into_panic();
}
#[tokio::test]
async fn job_handle_error_try_into_panic() {
let join_err = tokio::spawn(async { panic!("boom") }).await.unwrap_err();
let err: JobHandleError = join_err.into_panic().into();
assert!(err.try_into_panic().is_ok());
}
#[tokio::test]
async fn job_handle_error_panic_message() {
let join_err = tokio::spawn(async { panic!("boom") }).await.unwrap_err();
let err: JobHandleError = join_err.into_panic().into();
assert_eq!(Some("boom"), err.panic_message().as_deref());
}
}