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}