use std::time::Duration;
use qubit_function::{
BiConsumer,
BiFunction,
Consumer,
};
use super::{
RetryAfterHint,
RetryContext,
RetryListeners,
};
use crate::{
AttemptFailure,
AttemptFailureDecision,
RetryError,
};
#[derive(Clone)]
pub(crate) struct RetryEvents<E> {
retry_after_hint: Option<RetryAfterHint<E>>,
isolate_listener_panics: bool,
listeners: RetryListeners<E>,
}
impl<E> RetryEvents<E> {
#[inline]
pub(crate) fn new(
retry_after_hint: Option<RetryAfterHint<E>>,
isolate_listener_panics: bool,
listeners: RetryListeners<E>,
) -> Self {
Self {
retry_after_hint,
isolate_listener_panics,
listeners,
}
}
#[inline]
pub(crate) fn retry_after_hint(
&self,
failure: &AttemptFailure<E>,
context: &RetryContext,
) -> Option<Duration> {
self.retry_after_hint
.as_ref()
.and_then(|hint| self.invoke_listener(|| hint.apply(failure, context)))
}
pub(crate) fn failure_decision(
&self,
failure: &AttemptFailure<E>,
context: &RetryContext,
) -> AttemptFailureDecision {
let mut decision = AttemptFailureDecision::UseDefault;
for listener in &self.listeners.failure {
let current = self.invoke_listener(|| listener.apply(failure, context));
if current != AttemptFailureDecision::UseDefault {
decision = current;
}
}
decision
}
pub(crate) fn before_attempt(&self, context: &RetryContext) {
for listener in &self.listeners.before_attempt {
self.invoke_listener(|| {
listener.accept(context);
});
}
}
pub(crate) fn attempt_success(&self, context: &RetryContext) {
for listener in &self.listeners.attempt_success {
self.invoke_listener(|| {
listener.accept(context);
});
}
}
pub(crate) fn retry_scheduled(&self, failure: &AttemptFailure<E>, context: &RetryContext) {
for listener in &self.listeners.retry_scheduled {
self.invoke_listener(|| {
listener.accept(failure, context);
});
}
}
pub(crate) fn error(&self, error: RetryError<E>) -> RetryError<E> {
for listener in &self.listeners.error {
self.invoke_listener(|| {
listener.accept(&error, error.context());
});
}
error
}
fn invoke_listener<R>(&self, call: impl FnOnce() -> R) -> R
where
R: Default,
{
if self.isolate_listener_panics {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(call)).unwrap_or_default()
} else {
call()
}
}
}