qubit_cas/error/
cas_error.rs1use std::error::Error;
12use std::fmt;
13use std::sync::Arc;
14
15use qubit_retry::{AttemptFailure, RetryError, RetryErrorReason};
16
17use crate::event::CasContext;
18
19use super::{CasAttemptFailure, CasErrorKind};
20
21#[derive(Clone)]
23pub struct CasError<T, E> {
24 kind: CasErrorKind,
26 reason: RetryErrorReason,
28 context: CasContext,
30 last_failure: Option<CasAttemptFailure<T, E>>,
32}
33
34impl<T, E> CasError<T, E> {
35 #[inline]
44 pub(crate) fn new(
45 inner: RetryError<CasAttemptFailure<T, E>>,
46 attempt_timeout: Option<std::time::Duration>,
47 ) -> Self {
48 let (reason, raw_last_failure, retry_context) = inner.into_parts();
49 let context = CasContext::new(&retry_context, attempt_timeout);
50 let last_failure = match raw_last_failure {
51 Some(AttemptFailure::Error(failure)) => Some(failure),
52 Some(AttemptFailure::Timeout)
53 | Some(AttemptFailure::Panic(_))
54 | Some(AttemptFailure::Executor(_))
55 | None => None,
56 };
57 let kind = Self::classify_kind(reason, last_failure.as_ref());
58 Self {
59 kind,
60 reason,
61 context,
62 last_failure,
63 }
64 }
65
66 fn classify_kind(
75 reason: RetryErrorReason,
76 last_failure: Option<&CasAttemptFailure<T, E>>,
77 ) -> CasErrorKind {
78 match reason {
79 RetryErrorReason::Aborted => match last_failure {
80 Some(CasAttemptFailure::Timeout { .. }) => CasErrorKind::AttemptTimeout,
81 _ => CasErrorKind::Abort,
82 },
83 RetryErrorReason::AttemptsExceeded
84 | RetryErrorReason::UnsupportedOperation
85 | RetryErrorReason::WorkerStillRunning => match last_failure {
86 Some(CasAttemptFailure::Conflict { .. }) => CasErrorKind::Conflict,
87 Some(CasAttemptFailure::Timeout { .. }) => CasErrorKind::AttemptTimeout,
88 _ => CasErrorKind::RetryExhausted,
89 },
90 RetryErrorReason::MaxOperationElapsedExceeded => {
91 CasErrorKind::MaxOperationElapsedExceeded
92 }
93 RetryErrorReason::MaxTotalElapsedExceeded => CasErrorKind::MaxTotalElapsedExceeded,
94 }
95 }
96
97 #[inline]
103 pub fn kind(&self) -> CasErrorKind {
104 self.kind
105 }
106
107 #[inline]
112 pub fn reason(&self) -> RetryErrorReason {
113 self.reason
114 }
115
116 #[inline]
121 pub fn context(&self) -> CasContext {
122 self.context
123 }
124
125 #[inline]
130 pub fn attempts(&self) -> u32 {
131 self.context.attempt()
132 }
133
134 #[inline]
139 pub fn last_failure(&self) -> Option<&CasAttemptFailure<T, E>> {
140 self.last_failure.as_ref()
141 }
142
143 #[inline]
148 pub fn current(&self) -> Option<&Arc<T>> {
149 self.last_failure().map(CasAttemptFailure::current)
150 }
151
152 #[inline]
157 pub fn error(&self) -> Option<&E> {
158 self.last_failure().and_then(CasAttemptFailure::error)
159 }
160}
161
162impl<T, E> fmt::Debug for CasError<T, E> {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.debug_struct("CasError")
175 .field("kind", &self.kind())
176 .field("reason", &self.reason())
177 .field("context", &self.context())
178 .finish()
179 }
180}
181
182impl<T, E> fmt::Display for CasError<T, E>
183where
184 E: fmt::Display,
185{
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 let message = match self.kind() {
198 CasErrorKind::Abort => "CAS aborted",
199 CasErrorKind::Conflict => "CAS conflicts exhausted",
200 CasErrorKind::RetryExhausted => "CAS retryable failures exhausted",
201 CasErrorKind::AttemptTimeout => "CAS attempt timed out",
202 CasErrorKind::MaxOperationElapsedExceeded => "CAS max operation elapsed exceeded",
203 CasErrorKind::MaxTotalElapsedExceeded => "CAS max total elapsed exceeded",
204 };
205 write!(f, "{message} after {} attempt(s)", self.attempts())?;
206 if let Some(failure) = self.last_failure() {
207 write!(f, "; last failure: {failure}")?;
208 }
209 Ok(())
210 }
211}
212
213impl<T, E> Error for CasError<T, E>
214where
215 E: Error + 'static,
216{
217 fn source(&self) -> Option<&(dyn Error + 'static)> {
223 self.error().map(|error| error as &(dyn Error + 'static))
224 }
225}