use std::marker::PhantomData;
use std::time::Duration;
use qubit_error::BoxError;
use qubit_retry::{
AttemptTimeoutOption,
RetryBuilder,
RetryConfigError,
RetryDelay,
RetryJitter,
RetryOptions,
};
use crate::observability::{
CasObservabilityConfig,
ContentionThresholds,
ListenerPanicPolicy,
};
use crate::strategy::CasStrategy;
use super::cas_executor::CasExecutor;
pub struct CasBuilder<T, E = BoxError> {
retry: RetryBuilder<BoxError>,
observability: CasObservabilityConfig,
marker: PhantomData<fn() -> (T, E)>,
}
impl<T, E> CasBuilder<T, E> {
pub fn new() -> Self {
Self {
retry: RetryBuilder::new(),
observability: CasObservabilityConfig::default(),
marker: PhantomData,
}
}
pub fn options(mut self, options: RetryOptions) -> Self {
self.retry = self.retry.options(options);
self
}
pub fn max_attempts(mut self, max_attempts: u32) -> Self {
self.retry = self.retry.max_attempts(max_attempts);
self
}
#[inline]
pub fn max_retries(self, max_retries: u32) -> Self {
self.max_attempts(max_retries.saturating_add(1))
}
#[inline]
pub fn max_operation_elapsed(mut self, max_operation_elapsed: Option<Duration>) -> Self {
self.retry = self.retry.max_operation_elapsed(max_operation_elapsed);
self
}
#[inline]
pub fn max_total_elapsed(mut self, max_total_elapsed: Option<Duration>) -> Self {
self.retry = self.retry.max_total_elapsed(max_total_elapsed);
self
}
#[inline]
pub fn delay(mut self, delay: RetryDelay) -> Self {
self.retry = self.retry.delay(delay);
self
}
#[inline]
pub fn no_delay(self) -> Self {
self.delay(RetryDelay::none())
}
#[inline]
pub fn fixed_delay(self, delay: Duration) -> Self {
self.delay(RetryDelay::fixed(delay))
}
#[inline]
pub fn random_delay(self, min: Duration, max: Duration) -> Self {
self.delay(RetryDelay::random(min, max))
}
#[inline]
pub fn exponential_backoff(self, initial: Duration, max: Duration) -> Self {
self.exponential_backoff_with_multiplier(initial, max, 2.0)
}
#[inline]
pub fn exponential_backoff_with_multiplier(self, initial: Duration, max: Duration, multiplier: f64) -> Self {
self.delay(RetryDelay::exponential(initial, max, multiplier))
}
#[inline]
pub fn jitter(mut self, jitter: RetryJitter) -> Self {
self.retry = self.retry.jitter(jitter);
self
}
#[inline]
pub fn jitter_factor(self, factor: f64) -> Self {
self.jitter(RetryJitter::factor(factor))
}
#[inline]
pub fn attempt_timeout(mut self, attempt_timeout: Option<Duration>) -> Self {
self.retry = self.retry.attempt_timeout(attempt_timeout);
self
}
#[inline]
pub fn attempt_timeout_option(mut self, attempt_timeout: Option<AttemptTimeoutOption>) -> Self {
self.retry = self.retry.attempt_timeout_option(attempt_timeout);
self
}
#[inline]
pub fn retry_on_timeout(mut self) -> Self {
self.retry = self.retry.retry_on_timeout();
self
}
#[inline]
pub fn abort_on_timeout(mut self) -> Self {
self.retry = self.retry.abort_on_timeout();
self
}
pub fn strategy(self, strategy: CasStrategy) -> Self {
let profile = strategy.profile();
let builder = self
.max_attempts(profile.max_attempts())
.max_operation_elapsed(Some(profile.max_operation_elapsed()))
.max_total_elapsed(profile.max_total_elapsed());
if let Some((initial, max, jitter)) = strategy.backoff() {
builder.exponential_backoff(initial, max).jitter_factor(jitter)
} else {
builder.no_delay()
}
}
#[inline]
pub fn observability(mut self, observability: CasObservabilityConfig) -> Self {
self.observability = observability;
self
}
#[inline]
pub fn alert_on_contention(mut self, thresholds: ContentionThresholds) -> Self {
self.observability = self.observability.with_contention_thresholds(thresholds);
self
}
#[inline]
pub fn isolate_listener_panics(mut self) -> Self {
self.observability = self
.observability
.with_listener_panic_policy(ListenerPanicPolicy::Isolate);
self
}
pub fn build(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
let retry = self.retry.build()?;
Ok(CasExecutor::new(retry.options().clone(), self.observability))
}
pub fn build_contention_adaptive(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
self.strategy(CasStrategy::ContentionAdaptive).build()
}
pub fn build_latency_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
self.strategy(CasStrategy::LatencyFirst).build()
}
pub fn build_reliability_first(self) -> Result<CasExecutor<T, E>, RetryConfigError> {
self.strategy(CasStrategy::ReliabilityFirst).build()
}
}
impl<T, E> Default for CasBuilder<T, E> {
#[inline]
fn default() -> Self {
Self::new()
}
}