Skip to main content

qubit_cas/executor/
cas_builder.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//! Builder for [`crate::CasExecutor`].
11
12use std::marker::PhantomData;
13use std::time::Duration;
14
15use qubit_error::BoxError;
16use qubit_retry::{
17    AttemptTimeoutOption,
18    RetryBuilder,
19    RetryConfigError,
20    RetryDelay,
21    RetryJitter,
22    RetryOptions,
23};
24
25use crate::observability::{
26    CasObservabilityConfig,
27    ContentionThresholds,
28    ListenerPanicPolicy,
29};
30use crate::strategy::CasStrategy;
31
32use super::cas_executor::CasExecutor;
33
34/// Builder for [`CasExecutor`](crate::CasExecutor).
35pub struct CasBuilder<T, E = BoxError> {
36    /// Retry-layer builder that owns all retry-related settings.
37    retry: RetryBuilder<BoxError>,
38    /// Observability settings.
39    observability: CasObservabilityConfig,
40    /// Marker preserving the executor type parameters.
41    marker: PhantomData<fn() -> (T, E)>,
42}
43
44impl<T, E> CasBuilder<T, E> {
45    /// Creates a builder with default retry options.
46    ///
47    /// # Returns
48    /// A [`CasBuilder`] using [`RetryOptions::default`].
49    pub fn new() -> Self {
50        Self {
51            retry: RetryBuilder::new(),
52            observability: CasObservabilityConfig::default(),
53            marker: PhantomData,
54        }
55    }
56
57    /// Replaces builder settings from an existing retry option snapshot.
58    ///
59    /// # Parameters
60    /// - `options`: Retry option snapshot to install.
61    ///
62    /// # Returns
63    /// The updated builder.
64    pub fn options(mut self, options: RetryOptions) -> Self {
65        self.retry = self.retry.options(options);
66        self
67    }
68
69    /// Sets the maximum total attempts.
70    ///
71    /// # Parameters
72    /// - `max_attempts`: Maximum attempts, including the initial attempt.
73    ///
74    /// # Returns
75    /// The updated builder.
76    pub fn max_attempts(mut self, max_attempts: u32) -> Self {
77        self.retry = self.retry.max_attempts(max_attempts);
78        self
79    }
80
81    /// Sets the maximum retries after the initial attempt.
82    ///
83    /// # Parameters
84    /// - `max_retries`: Maximum retries after the first attempt.
85    ///
86    /// # Returns
87    /// The updated builder.
88    #[inline]
89    pub fn max_retries(self, max_retries: u32) -> Self {
90        self.max_attempts(max_retries.saturating_add(1))
91    }
92
93    /// Sets the maximum cumulative user operation elapsed-time budget.
94    ///
95    /// # Parameters
96    /// - `max_operation_elapsed`: Optional cumulative user operation time budget.
97    ///
98    /// # Returns
99    /// The updated builder.
100    #[inline]
101    pub fn max_operation_elapsed(mut self, max_operation_elapsed: Option<Duration>) -> Self {
102        self.retry = self.retry.max_operation_elapsed(max_operation_elapsed);
103        self
104    }
105
106    /// Sets the maximum monotonic elapsed-time budget for the whole retry flow.
107    ///
108    /// # Parameters
109    /// - `max_total_elapsed`: Optional total retry-flow time budget.
110    ///
111    /// # Returns
112    /// The updated builder.
113    #[inline]
114    pub fn max_total_elapsed(mut self, max_total_elapsed: Option<Duration>) -> Self {
115        self.retry = self.retry.max_total_elapsed(max_total_elapsed);
116        self
117    }
118
119    /// Sets the base retry delay strategy.
120    ///
121    /// # Parameters
122    /// - `delay`: Delay strategy used between failed attempts.
123    ///
124    /// # Returns
125    /// The updated builder.
126    #[inline]
127    pub fn delay(mut self, delay: RetryDelay) -> Self {
128        self.retry = self.retry.delay(delay);
129        self
130    }
131
132    /// Uses immediate retries with no sleep.
133    ///
134    /// # Returns
135    /// The updated builder.
136    #[inline]
137    pub fn no_delay(self) -> Self {
138        self.delay(RetryDelay::none())
139    }
140
141    /// Uses one fixed retry delay.
142    ///
143    /// # Parameters
144    /// - `delay`: Delay slept before each retry.
145    ///
146    /// # Returns
147    /// The updated builder.
148    #[inline]
149    pub fn fixed_delay(self, delay: Duration) -> Self {
150        self.delay(RetryDelay::fixed(delay))
151    }
152
153    /// Uses one random retry delay range.
154    ///
155    /// # Parameters
156    /// - `min`: Inclusive minimum delay.
157    /// - `max`: Inclusive maximum delay.
158    ///
159    /// # Returns
160    /// The updated builder.
161    #[inline]
162    pub fn random_delay(self, min: Duration, max: Duration) -> Self {
163        self.delay(RetryDelay::random(min, max))
164    }
165
166    /// Uses exponential backoff with multiplier `2.0`.
167    ///
168    /// # Parameters
169    /// - `initial`: Initial retry delay.
170    /// - `max`: Maximum retry delay.
171    ///
172    /// # Returns
173    /// The updated builder.
174    #[inline]
175    pub fn exponential_backoff(self, initial: Duration, max: Duration) -> Self {
176        self.exponential_backoff_with_multiplier(initial, max, 2.0)
177    }
178
179    /// Uses exponential backoff with a custom multiplier.
180    ///
181    /// # Parameters
182    /// - `initial`: Initial retry delay.
183    /// - `max`: Maximum retry delay.
184    /// - `multiplier`: Multiplier applied after each failed attempt.
185    ///
186    /// # Returns
187    /// The updated builder.
188    #[inline]
189    pub fn exponential_backoff_with_multiplier(self, initial: Duration, max: Duration, multiplier: f64) -> Self {
190        self.delay(RetryDelay::exponential(initial, max, multiplier))
191    }
192
193    /// Sets the jitter strategy.
194    ///
195    /// # Parameters
196    /// - `jitter`: Jitter strategy applied to retry delays.
197    ///
198    /// # Returns
199    /// The updated builder.
200    #[inline]
201    pub fn jitter(mut self, jitter: RetryJitter) -> Self {
202        self.retry = self.retry.jitter(jitter);
203        self
204    }
205
206    /// Sets relative jitter by factor.
207    ///
208    /// # Parameters
209    /// - `factor`: Relative jitter factor in `[0.0, 1.0]`.
210    ///
211    /// # Returns
212    /// The updated builder.
213    #[inline]
214    pub fn jitter_factor(self, factor: f64) -> Self {
215        self.jitter(RetryJitter::factor(factor))
216    }
217
218    /// Sets the async per-attempt timeout.
219    ///
220    /// # Parameters
221    /// - `attempt_timeout`: Timeout applied to each async CAS attempt.
222    ///
223    /// # Returns
224    /// The updated builder.
225    #[inline]
226    pub fn attempt_timeout(mut self, attempt_timeout: Option<Duration>) -> Self {
227        self.retry = self.retry.attempt_timeout(attempt_timeout);
228        self
229    }
230
231    /// Sets the complete retry-layer attempt timeout option.
232    ///
233    /// # Parameters
234    /// - `attempt_timeout`: Timeout option owned by the retry layer.
235    ///
236    /// # Returns
237    /// The updated builder.
238    #[inline]
239    pub fn attempt_timeout_option(mut self, attempt_timeout: Option<AttemptTimeoutOption>) -> Self {
240        self.retry = self.retry.attempt_timeout_option(attempt_timeout);
241        self
242    }
243
244    /// Retries attempts that exceed the configured timeout.
245    ///
246    /// # Returns
247    /// The updated builder.
248    #[inline]
249    pub fn retry_on_timeout(mut self) -> Self {
250        self.retry = self.retry.retry_on_timeout();
251        self
252    }
253
254    /// Aborts the CAS flow when one attempt exceeds the timeout.
255    ///
256    /// # Returns
257    /// The updated builder.
258    #[inline]
259    pub fn abort_on_timeout(mut self) -> Self {
260        self.retry = self.retry.abort_on_timeout();
261        self
262    }
263
264    /// Applies a built-in CAS strategy to this builder.
265    ///
266    /// # Parameters
267    /// - `strategy`: Strategy profile to install.
268    ///
269    /// # Returns
270    /// The updated builder.
271    pub fn strategy(self, strategy: CasStrategy) -> Self {
272        let profile = strategy.profile();
273        let builder = self
274            .max_attempts(profile.max_attempts())
275            .max_operation_elapsed(Some(profile.max_operation_elapsed()))
276            .max_total_elapsed(profile.max_total_elapsed());
277        if let Some((initial, max, jitter)) = strategy.backoff() {
278            builder.exponential_backoff(initial, max).jitter_factor(jitter)
279        } else {
280            builder.no_delay()
281        }
282    }
283
284    /// Installs observability configuration.
285    ///
286    /// # Parameters
287    /// - `observability`: Observability settings to use.
288    ///
289    /// # Returns
290    /// The updated builder.
291    #[inline]
292    pub fn observability(mut self, observability: CasObservabilityConfig) -> Self {
293        self.observability = observability;
294        self
295    }
296
297    /// Enables contention alerting with the supplied thresholds.
298    ///
299    /// # Parameters
300    /// - `thresholds`: Thresholds used to classify hot contention.
301    ///
302    /// # Returns
303    /// The updated builder.
304    #[inline]
305    pub fn alert_on_contention(mut self, thresholds: ContentionThresholds) -> Self {
306        self.observability = self.observability.with_contention_thresholds(thresholds);
307        self
308    }
309
310    /// Enables retry-layer listener panic isolation.
311    ///
312    /// # Returns
313    /// The updated builder.
314    #[inline]
315    pub fn isolate_listener_panics(mut self) -> Self {
316        self.observability = self
317            .observability
318            .with_listener_panic_policy(ListenerPanicPolicy::Isolate);
319        self
320    }
321
322    /// Builds one executor after validating the settings.
323    ///
324    /// # Returns
325    /// A validated [`CasExecutor`].
326    ///
327    /// # Errors
328    /// Returns [`RetryConfigError`] when the configured retry settings are
329    /// invalid.
330    pub fn build(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
331        let retry = self.retry.build()?;
332        Ok(CasExecutor::new(retry.options().clone(), self.observability))
333    }
334
335    /// Builds one executor with the contention-adaptive strategy.
336    ///
337    /// # Returns
338    /// A configured [`CasExecutor`] suitable for contended writers.
339    pub fn build_contention_adaptive(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
340        self.strategy(CasStrategy::ContentionAdaptive).build()
341    }
342
343    /// Builds one executor with the latency-first strategy.
344    ///
345    /// # Returns
346    /// A configured [`CasExecutor`] optimized for low latency.
347    pub fn build_latency_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
348        self.strategy(CasStrategy::LatencyFirst).build()
349    }
350
351    /// Builds one executor with the reliability-first strategy.
352    ///
353    /// # Returns
354    /// A configured [`CasExecutor`] optimized for long retry windows.
355    pub fn build_reliability_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
356        self.strategy(CasStrategy::ReliabilityFirst).build()
357    }
358}
359
360impl<T, E> Default for CasBuilder<T, E> {
361    /// Creates a default CAS builder.
362    ///
363    /// # Returns
364    /// A builder equivalent to [`CasBuilder::new`].
365    #[inline]
366    fn default() -> Self {
367        Self::new()
368    }
369}