use std::error::Error;
use std::fmt;
use std::sync::Arc;
use qubit_retry::{AttemptFailure, RetryError, RetryErrorReason};
use crate::event::CasContext;
use super::CasAttemptFailure;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CasErrorKind {
Abort,
Conflict,
RetryExhausted,
AttemptTimeout,
MaxElapsedExceeded,
}
#[derive(Clone)]
pub struct CasError<T, E> {
kind: CasErrorKind,
reason: RetryErrorReason,
context: CasContext,
last_failure: Option<CasAttemptFailure<T, E>>,
}
impl<T, E> CasError<T, E> {
#[inline]
pub(crate) fn new(
inner: RetryError<CasAttemptFailure<T, E>>,
attempt_timeout: Option<std::time::Duration>,
) -> Self {
let (reason, raw_last_failure, retry_context) = inner.into_parts();
let context = CasContext::new(&retry_context, attempt_timeout);
let last_failure = match raw_last_failure {
Some(AttemptFailure::Error(failure)) => Some(failure),
Some(AttemptFailure::Timeout)
| Some(AttemptFailure::Panic(_))
| Some(AttemptFailure::Executor(_))
| None => None,
};
let kind = Self::classify_kind(reason, last_failure.as_ref());
Self {
kind,
reason,
context,
last_failure,
}
}
fn classify_kind(
reason: RetryErrorReason,
last_failure: Option<&CasAttemptFailure<T, E>>,
) -> CasErrorKind {
match reason {
RetryErrorReason::Aborted => match last_failure {
Some(CasAttemptFailure::Timeout { .. }) => CasErrorKind::AttemptTimeout,
_ => CasErrorKind::Abort,
},
RetryErrorReason::AttemptsExceeded
| RetryErrorReason::UnsupportedOperation
| RetryErrorReason::WorkerStillRunning => match last_failure {
Some(CasAttemptFailure::Conflict { .. }) => CasErrorKind::Conflict,
Some(CasAttemptFailure::Timeout { .. }) => CasErrorKind::AttemptTimeout,
_ => CasErrorKind::RetryExhausted,
},
RetryErrorReason::MaxElapsedExceeded => CasErrorKind::MaxElapsedExceeded,
}
}
#[inline]
pub fn kind(&self) -> CasErrorKind {
self.kind
}
#[inline]
pub fn reason(&self) -> RetryErrorReason {
self.reason
}
#[inline]
pub fn context(&self) -> CasContext {
self.context
}
#[inline]
pub fn attempts(&self) -> u32 {
self.context.attempt()
}
#[inline]
pub fn last_failure(&self) -> Option<&CasAttemptFailure<T, E>> {
self.last_failure.as_ref()
}
#[inline]
pub fn current(&self) -> Option<&Arc<T>> {
self.last_failure().map(CasAttemptFailure::current)
}
#[inline]
pub fn error(&self) -> Option<&E> {
self.last_failure().and_then(CasAttemptFailure::error)
}
}
impl<T, E> fmt::Debug for CasError<T, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CasError")
.field("kind", &self.kind())
.field("reason", &self.reason())
.field("context", &self.context())
.finish()
}
}
impl<T, E> fmt::Display for CasError<T, E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self.kind() {
CasErrorKind::Abort => "CAS aborted",
CasErrorKind::Conflict => "CAS conflicts exhausted",
CasErrorKind::RetryExhausted => "CAS retryable failures exhausted",
CasErrorKind::AttemptTimeout => "CAS attempt timed out",
CasErrorKind::MaxElapsedExceeded => "CAS max elapsed exceeded",
};
write!(f, "{message} after {} attempt(s)", self.attempts())?;
if let Some(failure) = self.last_failure() {
write!(f, "; last failure: {failure}")?;
}
Ok(())
}
}
impl<T, E> Error for CasError<T, E>
where
E: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.error().map(|error| error as &(dyn Error + 'static))
}
}