@@ -38,28 +38,53 @@ lazy_static! {
3838 Arc :: new( FailFastRetryStrategy :: default ( ) ) ;
3939}
4040
41+ /// The reason an operation is being retried.
42+ ///
43+ /// Each variant identifies a specific transient failure condition that triggered
44+ /// a retry. The SDK passes this to [`RetryStrategy::retry_after`] so the strategy
45+ /// can decide whether (and how long) to wait before retrying.
4146#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
4247#[ non_exhaustive]
4348pub enum RetryReason {
49+ /// The server indicated the vBucket is not owned by this node.
4450 KvNotMyVbucket ,
51+ /// The vBucket map is invalid and must be refreshed.
4552 KvInvalidVbucketMap ,
53+ /// A temporary failure occurred on the KV engine.
4654 KvTemporaryFailure ,
55+ /// The collection ID is outdated and must be re-resolved.
4756 KvCollectionOutdated ,
57+ /// The server error map indicated the operation should be retried.
4858 KvErrorMapRetryIndicated ,
59+ /// The document is locked by another operation.
4960 KvLocked ,
61+ /// A sync-write (durable operation) is already in progress on this key.
5062 KvSyncWriteInProgress ,
63+ /// A sync-write recommit is in progress on this key.
5164 KvSyncWriteRecommitInProgress ,
65+ /// The required service is temporarily unavailable.
5266 ServiceNotAvailable ,
67+ /// The connection was closed while the request was in flight.
5368 SocketClosedWhileInFlight ,
69+ /// No connection is currently available.
5470 SocketNotAvailable ,
71+ /// A prepared statement for the query was invalidated.
5572 QueryPreparedStatementFailure ,
73+ /// The query index was not found (may still be building).
5674 QueryIndexNotFound ,
75+ /// The search service is rejecting requests due to rate limiting.
5776 SearchTooManyRequests ,
77+ /// An HTTP request failed to send.
5878 HttpSendRequestFailed ,
79+ /// The SDK is not yet ready to perform the operation.
5980 NotReady ,
6081}
6182
6283impl RetryReason {
84+ /// Returns `true` if this reason allows retrying non-idempotent operations.
85+ ///
86+ /// Most retry reasons are safe for non-idempotent retries because the
87+ /// server never processed the original request.
6388 pub fn allows_non_idempotent_retry ( & self ) -> bool {
6489 matches ! (
6590 self ,
@@ -81,6 +106,8 @@ impl RetryReason {
81106 )
82107 }
83108
109+ /// Returns `true` if the SDK should always retry for this reason,
110+ /// regardless of the retry strategy's decision.
84111 pub fn always_retry ( & self ) -> bool {
85112 matches ! (
86113 self ,
@@ -118,26 +145,70 @@ impl Display for RetryReason {
118145 }
119146}
120147
148+ /// The action a [`RetryStrategy`] returns to indicate when to retry.
149+ ///
150+ /// Contains the [`Duration`] to wait before the next retry attempt.
121151#[ derive( Clone , Debug ) ]
122152pub struct RetryAction {
153+ /// How long to wait before retrying.
123154 pub duration : Duration ,
124155}
125156
126157impl RetryAction {
158+ /// Creates a new `RetryAction` with the given backoff duration.
127159 pub fn new ( duration : Duration ) -> Self {
128160 Self { duration }
129161 }
130162}
131163
164+ /// A strategy that decides whether and when to retry a failed operation.
165+ ///
166+ /// Implement this trait to provide custom retry behavior. The SDK calls
167+ /// [`retry_after`](RetryStrategy::retry_after) each time a retryable failure
168+ /// occurs, passing the request metadata and the reason for the failure.
169+ ///
170+ /// Return `Some(RetryAction)` to retry after the specified duration,
171+ /// or `None` to stop retrying and propagate the error.
172+ ///
173+ /// # Example
174+ ///
175+ /// ```rust
176+ /// use couchbase_core::retry::{RetryStrategy, RetryAction, RetryRequest, RetryReason};
177+ /// use std::fmt::Debug;
178+ /// use std::time::Duration;
179+ ///
180+ /// #[derive(Debug)]
181+ /// struct FixedDelayRetry(Duration);
182+ ///
183+ /// impl RetryStrategy for FixedDelayRetry {
184+ /// fn retry_after(&self, request: &RetryRequest, reason: &RetryReason) -> Option<RetryAction> {
185+ /// if request.retry_attempts < 3 {
186+ /// Some(RetryAction::new(self.0))
187+ /// } else {
188+ /// None // give up after 3 attempts
189+ /// }
190+ /// }
191+ /// }
192+ /// ```
132193pub trait RetryStrategy : Debug + Send + Sync {
194+ /// Decides whether to retry an operation and how long to wait.
195+ ///
196+ /// * `request` — Metadata about the in-flight request (attempt count, idempotency, etc.).
197+ /// * `reason` — Why the operation failed.
198+ ///
199+ /// Return `Some(RetryAction)` to retry, or `None` to stop.
133200 fn retry_after ( & self , request : & RetryRequest , reason : & RetryReason ) -> Option < RetryAction > ;
134201}
135202
203+ /// Metadata about a request that is being considered for retry.
136204#[ derive( Clone , Debug ) ]
137205pub struct RetryRequest {
138206 pub ( crate ) operation : & ' static str ,
207+ /// Whether the operation is idempotent (safe to retry without side effects).
139208 pub is_idempotent : bool ,
209+ /// The number of retry attempts that have already been made.
140210 pub retry_attempts : u32 ,
211+ /// The set of reasons this request has been retried so far.
141212 pub retry_reasons : HashSet < RetryReason > ,
142213 pub ( crate ) unique_id : Option < String > ,
143214}
0 commit comments