use std::time::{
Duration,
Instant,
};
use super::attempt::Attempt;
use super::retry::Retry;
use super::retry_failure_handler::RetryFailureHandler;
use super::retry_flow_action::RetryFlowAction;
use super::retry_flow_state::RetryFlowState;
use super::value_operation::ValueOperation;
use crate::options::EffectiveAttemptTimeout;
use crate::{
AttemptTimeoutSource,
RetryError,
RetryErrorReason,
};
pub(in crate::executor) struct RetryRunner<'a, E> {
retry: &'a Retry<E>,
}
#[allow(clippy::result_large_err)]
impl<'a, E> RetryRunner<'a, E> {
#[inline]
pub(in crate::executor) fn new(retry: &'a Retry<E>) -> Self {
Self { retry }
}
pub(in crate::executor) fn run<T, F>(&self, mut operation: F) -> Result<T, RetryError<E>>
where
F: FnMut() -> Result<T, E>,
{
if self.retry.options().attempt_timeout().is_some() {
return Err(self.unsupported_attempt_timeout_error());
}
let mut operation = ValueOperation::new(&mut operation);
self.run_operation(&mut operation).map(|()| operation.into_value())
}
fn run_operation(&self, operation: &mut dyn Attempt<E>) -> Result<(), RetryError<E>> {
let options = self.retry.options();
let events = self.retry.events();
let handler = RetryFailureHandler::new(options, events);
let no_timeout = EffectiveAttemptTimeout::none();
let mut state = RetryFlowState::new();
loop {
if let Some(error) = state.take_elapsed_error(options, no_timeout) {
return Err(events.error(error));
}
state.start_next_attempt();
let context = state.context(options, Duration::ZERO, no_timeout);
events.before_attempt(&context);
if let Some(error) = state.take_elapsed_error(options, no_timeout) {
return Err(events.error(error));
}
let attempt_start = Instant::now();
match operation.call() {
Ok(()) => {
let attempt_elapsed = attempt_start.elapsed();
state.add_operation_elapsed(attempt_elapsed);
let context = state.context(options, attempt_elapsed, no_timeout);
events.attempt_success(&context);
return Ok(());
}
Err(failure) => {
let attempt_elapsed = attempt_start.elapsed();
state.add_operation_elapsed(attempt_elapsed);
let context = state.context(options, attempt_elapsed, no_timeout);
match handler.handle(&state, failure, context, None) {
RetryFlowAction::Retry { delay, failure } => {
sleep_blocking(delay);
state.record_last_failure(failure);
}
RetryFlowAction::Finished(error) => return Err(events.error(error)),
}
}
}
}
}
fn unsupported_attempt_timeout_error(&self) -> RetryError<E> {
let options = self.retry.options();
let state: RetryFlowState<E> = RetryFlowState::new();
let attempt_timeout = EffectiveAttemptTimeout::new(
options.attempt_timeout_duration(),
Some(AttemptTimeoutSource::Configured),
);
self.retry.events().error(RetryError::new(
RetryErrorReason::UnsupportedOperation,
None,
state.context(options, Duration::ZERO, attempt_timeout),
))
}
}
pub(in crate::executor) fn sleep_blocking(delay: Duration) {
if !delay.is_zero() {
std::thread::sleep(delay);
}
}