#[cfg(feature = "tokio")]
use std::future::Future;
use std::time::{
Duration,
Instant,
};
#[cfg(feature = "tokio")]
use super::async_attempt::AsyncAttempt;
#[cfg(feature = "tokio")]
use super::async_value_operation::AsyncValueOperation;
use super::effective_attempt_timeout::EffectiveAttemptTimeout;
use super::retry::{
Retry,
sleep_blocking,
};
use super::retry_flow_action::RetryFlowAction;
use super::retry_flow_state::RetryFlowState;
use super::sync_attempt::SyncAttempt;
use super::sync_value_operation::SyncValueOperation;
#[cfg(feature = "tokio")]
use crate::AttemptFailure;
use crate::event::RetryContextParts;
use crate::{
AttemptTimeoutSource,
RetryContext,
RetryError,
RetryErrorReason,
};
#[allow(clippy::result_large_err)]
impl<E> Retry<E> {
pub fn run<T, F>(&self, mut operation: F) -> Result<T, RetryError<E>>
where
F: FnMut() -> Result<T, E>,
{
if self.options().attempt_timeout().is_some() {
let attempt_timeout = self.attempt_timeout_duration();
return Err(self.emit_error(RetryError::new(
RetryErrorReason::UnsupportedOperation,
None,
RetryContext::from_parts(RetryContextParts {
attempt: 0,
max_attempts: self.options().max_attempts(),
max_operation_elapsed: self.options().max_operation_elapsed(),
max_total_elapsed: self.options().max_total_elapsed(),
operation_elapsed: Duration::ZERO,
total_elapsed: Duration::ZERO,
attempt_elapsed: Duration::ZERO,
attempt_timeout,
})
.with_attempt_timeout_source(Some(AttemptTimeoutSource::Configured)),
)));
}
let mut operation = SyncValueOperation::new(&mut operation);
match self.run_sync_operation(&mut operation) {
Ok(()) => Ok(operation.into_value()),
Err(error) => Err(error),
}
}
fn run_sync_operation(&self, operation: &mut dyn SyncAttempt<E>) -> Result<(), RetryError<E>> {
let mut state = RetryFlowState::new();
loop {
self.ensure_elapsed_budget_available(
&mut state,
EffectiveAttemptTimeout::new(None, None),
)?;
self.emit_before_attempt_for_next_attempt(
&mut state,
EffectiveAttemptTimeout::new(None, None),
);
self.ensure_elapsed_budget_available(
&mut state,
EffectiveAttemptTimeout::new(None, None),
)?;
let attempt_start = Instant::now();
match operation.call() {
Ok(()) => {
let attempt_elapsed = attempt_start.elapsed();
state.add_operation_elapsed(attempt_elapsed);
let context = self.context_from_state(&state, attempt_elapsed, None);
self.emit_attempt_success(&context);
return Ok(());
}
Err(failure) => {
let attempt_elapsed = attempt_start.elapsed();
state.add_operation_elapsed(attempt_elapsed);
let context = self.context_from_state(&state, attempt_elapsed, None);
match self.handle_failure(
state.attempts,
failure,
context,
None,
state.started_at,
) {
RetryFlowAction::Retry { delay, failure } => {
sleep_blocking(delay);
state.record_last_failure(failure);
}
RetryFlowAction::Finished(error) => return Err(self.emit_error(error)),
}
}
}
}
}
}
#[cfg(feature = "tokio")]
#[allow(clippy::result_large_err)]
impl<E> Retry<E> {
pub async fn run_async<T, F, Fut>(&self, mut operation: F) -> Result<T, RetryError<E>>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
let mut operation = AsyncValueOperation::new(&mut operation);
self.run_async_operation(&mut operation)
.await
.map(|()| operation.into_value())
}
async fn run_async_operation(
&self,
operation: &mut dyn AsyncAttempt<E>,
) -> Result<(), RetryError<E>> {
let mut state = RetryFlowState::new();
loop {
let attempt_timeout =
self.effective_attempt_timeout(state.operation_elapsed, state.total_elapsed());
self.ensure_elapsed_budget_available(&mut state, attempt_timeout)?;
let attempt_timeout =
self.effective_attempt_timeout(state.operation_elapsed, state.total_elapsed());
self.emit_before_attempt_for_next_attempt(&mut state, attempt_timeout);
let attempt_timeout =
self.effective_attempt_timeout(state.operation_elapsed, state.total_elapsed());
self.ensure_elapsed_budget_available(&mut state, attempt_timeout)?;
let attempt_start = Instant::now();
let result = if let Some(timeout) = attempt_timeout.duration {
match tokio::time::timeout(timeout, operation.call()).await {
Ok(result) => result,
Err(_) => Err(AttemptFailure::Timeout),
}
} else {
operation.call().await
};
let attempt_elapsed = attempt_start.elapsed();
state.add_operation_elapsed(attempt_elapsed);
let context = self
.context_from_state(&state, attempt_elapsed, attempt_timeout.duration)
.with_attempt_timeout_source(attempt_timeout.source);
match result {
Ok(()) => {
self.emit_attempt_success(&context);
return Ok(());
}
Err(failure) => {
if let Some(reason) = attempt_timeout.elapsed_timeout_reason(&failure) {
return Err(self.emit_error(RetryError::new(
reason,
Some(failure),
context,
)));
}
match self.handle_failure(
state.attempts,
failure,
context,
None,
state.started_at,
) {
RetryFlowAction::Retry { delay, failure } => {
sleep_async(delay).await;
state.record_last_failure(failure);
}
RetryFlowAction::Finished(error) => return Err(self.emit_error(error)),
}
}
}
}
}
}
#[cfg(feature = "tokio")]
async fn sleep_async(delay: Duration) {
if !delay.is_zero() {
tokio::time::sleep(delay).await;
}
}