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(
190        self,
191        initial: Duration,
192        max: Duration,
193        multiplier: f64,
194    ) -> Self {
195        self.delay(RetryDelay::exponential(initial, max, multiplier))
196    }
197
198    /// Sets the jitter strategy.
199    ///
200    /// # Parameters
201    /// - `jitter`: Jitter strategy applied to retry delays.
202    ///
203    /// # Returns
204    /// The updated builder.
205    #[inline]
206    pub fn jitter(mut self, jitter: RetryJitter) -> Self {
207        self.retry = self.retry.jitter(jitter);
208        self
209    }
210
211    /// Sets relative jitter by factor.
212    ///
213    /// # Parameters
214    /// - `factor`: Relative jitter factor in `[0.0, 1.0]`.
215    ///
216    /// # Returns
217    /// The updated builder.
218    #[inline]
219    pub fn jitter_factor(self, factor: f64) -> Self {
220        self.jitter(RetryJitter::factor(factor))
221    }
222
223    /// Sets the async per-attempt timeout.
224    ///
225    /// # Parameters
226    /// - `attempt_timeout`: Timeout applied to each async CAS attempt.
227    ///
228    /// # Returns
229    /// The updated builder.
230    #[inline]
231    pub fn attempt_timeout(mut self, attempt_timeout: Option<Duration>) -> Self {
232        self.retry = self.retry.attempt_timeout(attempt_timeout);
233        self
234    }
235
236    /// Sets the complete retry-layer attempt timeout option.
237    ///
238    /// # Parameters
239    /// - `attempt_timeout`: Timeout option owned by the retry layer.
240    ///
241    /// # Returns
242    /// The updated builder.
243    #[inline]
244    pub fn attempt_timeout_option(mut self, attempt_timeout: Option<AttemptTimeoutOption>) -> Self {
245        self.retry = self.retry.attempt_timeout_option(attempt_timeout);
246        self
247    }
248
249    /// Retries attempts that exceed the configured timeout.
250    ///
251    /// # Returns
252    /// The updated builder.
253    #[inline]
254    pub fn retry_on_timeout(mut self) -> Self {
255        self.retry = self.retry.retry_on_timeout();
256        self
257    }
258
259    /// Aborts the CAS flow when one attempt exceeds the timeout.
260    ///
261    /// # Returns
262    /// The updated builder.
263    #[inline]
264    pub fn abort_on_timeout(mut self) -> Self {
265        self.retry = self.retry.abort_on_timeout();
266        self
267    }
268
269    /// Applies a built-in CAS strategy to this builder.
270    ///
271    /// # Parameters
272    /// - `strategy`: Strategy profile to install.
273    ///
274    /// # Returns
275    /// The updated builder.
276    pub fn strategy(self, strategy: CasStrategy) -> Self {
277        let profile = strategy.profile();
278        let builder = self
279            .max_attempts(profile.max_attempts())
280            .max_operation_elapsed(Some(profile.max_operation_elapsed()))
281            .max_total_elapsed(profile.max_total_elapsed());
282        if let Some((initial, max, jitter)) = strategy.backoff() {
283            builder
284                .exponential_backoff(initial, max)
285                .jitter_factor(jitter)
286        } else {
287            builder.no_delay()
288        }
289    }
290
291    /// Installs observability configuration.
292    ///
293    /// # Parameters
294    /// - `observability`: Observability settings to use.
295    ///
296    /// # Returns
297    /// The updated builder.
298    #[inline]
299    pub fn observability(mut self, observability: CasObservabilityConfig) -> Self {
300        self.observability = observability;
301        self
302    }
303
304    /// Enables contention alerting with the supplied thresholds.
305    ///
306    /// # Parameters
307    /// - `thresholds`: Thresholds used to classify hot contention.
308    ///
309    /// # Returns
310    /// The updated builder.
311    #[inline]
312    pub fn alert_on_contention(mut self, thresholds: ContentionThresholds) -> Self {
313        self.observability = self.observability.with_contention_thresholds(thresholds);
314        self
315    }
316
317    /// Enables retry-layer listener panic isolation.
318    ///
319    /// # Returns
320    /// The updated builder.
321    #[inline]
322    pub fn isolate_listener_panics(mut self) -> Self {
323        self.observability = self
324            .observability
325            .with_listener_panic_policy(ListenerPanicPolicy::Isolate);
326        self
327    }
328
329    /// Builds one executor after validating the settings.
330    ///
331    /// # Returns
332    /// A validated [`CasExecutor`].
333    ///
334    /// # Errors
335    /// Returns [`RetryConfigError`] when the configured retry settings are
336    /// invalid.
337    pub fn build(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
338        let retry = self.retry.build()?;
339        Ok(CasExecutor::new(
340            retry.options().clone(),
341            self.observability,
342        ))
343    }
344
345    /// Builds one executor with the contention-adaptive strategy.
346    ///
347    /// # Returns
348    /// A configured [`CasExecutor`] suitable for contended writers.
349    pub fn build_contention_adaptive(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
350        self.strategy(CasStrategy::ContentionAdaptive).build()
351    }
352
353    /// Builds one executor with the latency-first strategy.
354    ///
355    /// # Returns
356    /// A configured [`CasExecutor`] optimized for low latency.
357    pub fn build_latency_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
358        self.strategy(CasStrategy::LatencyFirst).build()
359    }
360
361    /// Builds one executor with the reliability-first strategy.
362    ///
363    /// # Returns
364    /// A configured [`CasExecutor`] optimized for long retry windows.
365    pub fn build_reliability_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
366        self.strategy(CasStrategy::ReliabilityFirst).build()
367    }
368}
369
370impl<T, E> Default for CasBuilder<T, E> {
371    /// Creates a default CAS builder.
372    ///
373    /// # Returns
374    /// A builder equivalent to [`CasBuilder::new`].
375    #[inline]
376    fn default() -> Self {
377        Self::new()
378    }
379}