qubit_cas/error/cas_attempt_failure.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Attempt-level CAS failures.
11
12use std::fmt;
13use std::sync::Arc;
14
15use super::cas_attempt_failure_kind::CasAttemptFailureKind;
16
17/// Failure produced by one CAS attempt.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum CasAttemptFailure<T, E> {
20 /// Compare-and-swap failed because another writer changed the state first.
21 Conflict {
22 /// State observed after the conflict.
23 current: Arc<T>,
24 },
25
26 /// Business logic requested another attempt.
27 Retry {
28 /// Current state for the failed attempt.
29 current: Arc<T>,
30 /// Retryable business error.
31 error: E,
32 },
33
34 /// Business logic aborted the CAS flow.
35 Abort {
36 /// Current state for the failed attempt.
37 current: Arc<T>,
38 /// Terminal business error.
39 error: E,
40 },
41
42 /// One async attempt exceeded the configured timeout.
43 Timeout {
44 /// State snapshot used by the timed-out attempt.
45 current: Arc<T>,
46 },
47}
48
49impl<T, E> CasAttemptFailure<T, E> {
50 /// Creates a conflict failure.
51 ///
52 /// # Parameters
53 /// - `current`: Current state observed after the conflict.
54 ///
55 /// # Returns
56 /// A [`CasAttemptFailure::Conflict`] value.
57 #[inline]
58 pub(crate) fn conflict(current: Arc<T>) -> Self {
59 Self::Conflict { current }
60 }
61
62 /// Creates a retryable business failure.
63 ///
64 /// # Parameters
65 /// - `current`: Current state for the failed attempt.
66 /// - `error`: Retryable business error.
67 ///
68 /// # Returns
69 /// A [`CasAttemptFailure::Retry`] value.
70 #[inline]
71 pub(crate) fn retry(current: Arc<T>, error: E) -> Self {
72 Self::Retry { current, error }
73 }
74
75 /// Creates an abort failure.
76 ///
77 /// # Parameters
78 /// - `current`: Current state for the failed attempt.
79 /// - `error`: Terminal business error.
80 ///
81 /// # Returns
82 /// A [`CasAttemptFailure::Abort`] value.
83 #[inline]
84 pub(crate) fn abort(current: Arc<T>, error: E) -> Self {
85 Self::Abort { current, error }
86 }
87
88 /// Creates a timeout failure.
89 ///
90 /// # Parameters
91 /// - `current`: State snapshot used by the timed-out attempt.
92 ///
93 /// # Returns
94 /// A [`CasAttemptFailure::Timeout`] value.
95 #[inline]
96 #[cfg(feature = "tokio")]
97 pub(crate) fn timeout(current: Arc<T>) -> Self {
98 Self::Timeout { current }
99 }
100
101 /// Returns the state snapshot associated with this failure.
102 ///
103 /// # Returns
104 /// Shared reference to the current state.
105 #[inline]
106 pub fn current(&self) -> &Arc<T> {
107 match self {
108 Self::Conflict { current }
109 | Self::Retry { current, .. }
110 | Self::Abort { current, .. }
111 | Self::Timeout { current } => current,
112 }
113 }
114
115 /// Returns the lightweight kind of this attempt failure.
116 ///
117 /// # Returns
118 /// The [`CasAttemptFailureKind`] matching this failure variant.
119 #[inline]
120 pub fn kind(&self) -> CasAttemptFailureKind {
121 match self {
122 Self::Conflict { .. } => CasAttemptFailureKind::Conflict,
123 Self::Retry { .. } => CasAttemptFailureKind::Retry,
124 Self::Abort { .. } => CasAttemptFailureKind::Abort,
125 Self::Timeout { .. } => CasAttemptFailureKind::Timeout,
126 }
127 }
128
129 /// Returns the business error when this failure carries one.
130 ///
131 /// # Returns
132 /// `Some(&E)` for [`CasAttemptFailure::Retry`] and
133 /// [`CasAttemptFailure::Abort`], or `None` otherwise.
134 #[inline]
135 pub fn error(&self) -> Option<&E> {
136 match self {
137 Self::Retry { error, .. } | Self::Abort { error, .. } => Some(error),
138 Self::Conflict { .. } | Self::Timeout { .. } => None,
139 }
140 }
141
142 /// Returns whether this failure is a compare-and-swap conflict.
143 ///
144 /// # Returns
145 /// `true` for [`CasAttemptFailure::Conflict`].
146 #[inline]
147 pub fn is_conflict(&self) -> bool {
148 matches!(self, Self::Conflict { .. })
149 }
150
151 /// Returns whether this failure is a retryable business error.
152 ///
153 /// # Returns
154 /// `true` for [`CasAttemptFailure::Retry`].
155 #[inline]
156 pub fn is_retry(&self) -> bool {
157 matches!(self, Self::Retry { .. })
158 }
159
160 /// Returns whether this failure aborts the CAS flow.
161 ///
162 /// # Returns
163 /// `true` for [`CasAttemptFailure::Abort`].
164 #[inline]
165 pub fn is_abort(&self) -> bool {
166 matches!(self, Self::Abort { .. })
167 }
168
169 /// Returns whether this failure came from an async timeout.
170 ///
171 /// # Returns
172 /// `true` for [`CasAttemptFailure::Timeout`].
173 #[inline]
174 pub fn is_timeout(&self) -> bool {
175 matches!(self, Self::Timeout { .. })
176 }
177}
178
179impl<T, E> fmt::Display for CasAttemptFailure<T, E>
180where
181 E: fmt::Display,
182{
183 /// Formats the attempt failure for diagnostics.
184 ///
185 /// # Parameters
186 /// - `f`: Formatter provided by the standard formatting machinery.
187 ///
188 /// # Returns
189 /// `fmt::Result` from the formatter.
190 ///
191 /// # Errors
192 /// Returns a formatting error if the formatter fails.
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 Self::Conflict { .. } => write!(f, "compare-and-swap conflict"),
196 Self::Retry { error, .. } => write!(f, "retryable CAS failure: {error}"),
197 Self::Abort { error, .. } => write!(f, "aborted CAS failure: {error}"),
198 Self::Timeout { .. } => write!(f, "CAS attempt timed out"),
199 }
200 }
201}