use core::fmt;
use core::future::Future;
use core::marker::PhantomData;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project_lite::pin_project;
use crate::compat::Duration;
use crate::error::RetryError;
use crate::policy::HookChain;
use crate::policy::{
AttemptHook, BeforeAttemptHook, ExecutionHooks, ExitHook, PolicyHandle, RetryPolicy,
};
use crate::predicate::Predicate;
use crate::sleep::Sleeper;
use crate::state::{AttemptState, ExitState, RetryState};
use crate::stats::RetryStats;
use crate::stop::Stop;
use crate::wait::Wait;
use super::common::{AsyncEngine, AsyncRetryOp};
use crate::policy::time::ElapsedTracker;
#[doc(hidden)]
pub struct NoAsyncSleep;
pub struct AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E> {
policy: Policy,
hooks: ExecutionHooks<BA, AA, OX>,
op: F,
sleeper: SleepImpl,
elapsed_tracker: ElapsedTracker,
timeout: Option<Duration>,
_marker: PhantomData<fn() -> (T, E)>,
}
pub struct AsyncRetryExecWithStats<Policy, BA, AA, OX, F, SleepImpl, T, E> {
inner: AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E>,
}
pub type AsyncRetry<'policy, S, W, P, BA, AA, OX, F, SleepImpl, T, E> =
AsyncRetryExec<&'policy RetryPolicy<S, W, P>, BA, AA, OX, F, SleepImpl, T, E>;
pub type AsyncRetryWithStats<'policy, S, W, P, BA, AA, OX, F, SleepImpl, T, E> =
AsyncRetryExecWithStats<&'policy RetryPolicy<S, W, P>, BA, AA, OX, F, SleepImpl, T, E>;
impl<Policy, BA, AA, OX, F, SleepImpl, T, E> fmt::Debug
for AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncRetryExec").finish_non_exhaustive()
}
}
impl<Policy, BA, AA, OX, F, SleepImpl, T, E> fmt::Debug
for AsyncRetryExecWithStats<Policy, BA, AA, OX, F, SleepImpl, T, E>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncRetryExecWithStats")
.finish_non_exhaustive()
}
}
impl<Policy, BA, AA, OX, F, SleepImpl, T, E>
AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E>
{
pub(crate) fn new(
policy: Policy,
hooks: ExecutionHooks<BA, AA, OX>,
op: F,
sleeper: SleepImpl,
elapsed_tracker: ElapsedTracker,
) -> Self {
Self {
policy,
hooks,
op,
sleeper,
elapsed_tracker,
timeout: None,
_marker: PhantomData,
}
}
fn map_hooks<NewBA, NewAA, NewOX>(
self,
map: impl FnOnce(ExecutionHooks<BA, AA, OX>) -> ExecutionHooks<NewBA, NewAA, NewOX>,
) -> AsyncRetryExec<Policy, NewBA, NewAA, NewOX, F, SleepImpl, T, E> {
AsyncRetryExec {
policy: self.policy,
hooks: map(self.hooks),
op: self.op,
sleeper: self.sleeper,
elapsed_tracker: self.elapsed_tracker,
timeout: self.timeout,
_marker: PhantomData,
}
}
pub(crate) fn map_policy<NewPolicy>(
self,
map: impl FnOnce(Policy) -> NewPolicy,
) -> AsyncRetryExec<NewPolicy, BA, AA, OX, F, SleepImpl, T, E> {
AsyncRetryExec {
policy: map(self.policy),
hooks: self.hooks,
op: self.op,
sleeper: self.sleeper,
elapsed_tracker: self.elapsed_tracker,
timeout: self.timeout,
_marker: PhantomData,
}
}
#[must_use]
pub fn elapsed_clock(mut self, clock: fn() -> Duration) -> Self {
self.elapsed_tracker = ElapsedTracker::new(Some(clock));
self
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn elapsed_clock_fn(mut self, clock: impl Fn() -> Duration + 'static) -> Self {
self.elapsed_tracker = ElapsedTracker::new_boxed(crate::compat::Box::new(clock));
self
}
#[must_use]
pub fn timeout(mut self, dur: Duration) -> Self {
self.timeout = Some(dur);
self
}
#[must_use]
pub fn with_stats(self) -> AsyncRetryExecWithStats<Policy, BA, AA, OX, F, SleepImpl, T, E> {
AsyncRetryExecWithStats { inner: self }
}
}
impl<Policy, BA, AA, OX, F, T, E> AsyncRetryExec<Policy, BA, AA, OX, F, NoAsyncSleep, T, E> {
#[must_use]
pub fn sleep<NewSleep>(
self,
sleeper: NewSleep,
) -> AsyncRetryExec<Policy, BA, AA, OX, F, NewSleep, T, E>
where
NewSleep: Sleeper,
{
AsyncRetryExec {
policy: self.policy,
hooks: self.hooks,
op: self.op,
sleeper,
elapsed_tracker: self.elapsed_tracker,
timeout: self.timeout,
_marker: PhantomData,
}
}
}
impl_hook_chain! {
impl[Policy, BA, AA, OX, F, SleepImpl, T, E]
AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E>
=>
before_attempt -> { AsyncRetryExec<Policy, HookChain<BA, Hook>, AA, OX, F, SleepImpl, T, E> },
after_attempt -> { AsyncRetryExec<Policy, BA, HookChain<AA, Hook>, OX, F, SleepImpl, T, E> },
on_exit -> { AsyncRetryExec<Policy, BA, AA, HookChain<OX, Hook>, F, SleepImpl, T, E> },
}
#[allow(private_bounds)]
impl<Policy, BA, AA, OX, F, SleepImpl, T, E>
AsyncRetryExec<Policy, BA, AA, OX, F, SleepImpl, T, E>
{
pub fn call<Fut>(self) -> impl Future<Output = Result<T, RetryError<T, E>>>
where
Policy: PolicyHandle,
Policy::Stop: Stop,
Policy::Wait: Wait,
Policy::Predicate: Predicate<T, E>,
BA: BeforeAttemptHook,
AA: AttemptHook<T, E>,
OX: ExitHook<T, E>,
F: AsyncRetryOp<T, E, Fut>,
Fut: Future<Output = Result<T, E>>,
SleepImpl: Sleeper,
{
AsyncRetryCall {
engine: self.into_engine(),
}
}
fn into_engine<Fut>(
self,
) -> AsyncEngine<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepImpl::Sleep>
where
SleepImpl: Sleeper,
{
AsyncEngine::new(
self.policy,
self.hooks,
self.op,
self.sleeper,
self.elapsed_tracker,
self.timeout,
)
}
}
#[allow(private_bounds)]
impl<Policy, BA, AA, OX, F, SleepImpl, T, E>
AsyncRetryExecWithStats<Policy, BA, AA, OX, F, SleepImpl, T, E>
{
pub fn call<Fut>(self) -> impl Future<Output = (Result<T, RetryError<T, E>>, RetryStats)>
where
Policy: PolicyHandle,
Policy::Stop: Stop,
Policy::Wait: Wait,
Policy::Predicate: Predicate<T, E>,
BA: BeforeAttemptHook,
AA: AttemptHook<T, E>,
OX: ExitHook<T, E>,
F: AsyncRetryOp<T, E, Fut>,
Fut: Future<Output = Result<T, E>>,
SleepImpl: Sleeper,
{
AsyncRetryCallWithStats {
engine: self.inner.into_engine(),
}
}
}
pin_project! {
struct AsyncRetryCall<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut> {
#[pin]
engine: AsyncEngine<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut>,
}
}
#[allow(private_bounds)]
impl<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut> Future
for AsyncRetryCall<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut>
where
Policy: PolicyHandle,
Policy::Stop: Stop,
Policy::Wait: Wait,
Policy::Predicate: Predicate<T, E>,
BA: BeforeAttemptHook,
AA: AttemptHook<T, E>,
OX: ExitHook<T, E>,
F: AsyncRetryOp<T, E, Fut>,
Fut: Future<Output = Result<T, E>>,
SleepImpl: Sleeper<Sleep = SleepFut>,
SleepFut: Future<Output = ()>,
{
type Output = Result<T, RetryError<T, E>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project()
.engine
.poll_step::<false>(cx)
.map(|(result, _stats)| result)
}
}
pin_project! {
struct AsyncRetryCallWithStats<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut> {
#[pin]
engine: AsyncEngine<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut>,
}
}
#[allow(private_bounds)]
impl<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut> Future
for AsyncRetryCallWithStats<Policy, BA, AA, OX, F, Fut, SleepImpl, T, E, SleepFut>
where
Policy: PolicyHandle,
Policy::Stop: Stop,
Policy::Wait: Wait,
Policy::Predicate: Predicate<T, E>,
BA: BeforeAttemptHook,
AA: AttemptHook<T, E>,
OX: ExitHook<T, E>,
F: AsyncRetryOp<T, E, Fut>,
Fut: Future<Output = Result<T, E>>,
SleepImpl: Sleeper<Sleep = SleepFut>,
SleepFut: Future<Output = ()>,
{
type Output = (Result<T, RetryError<T, E>>, RetryStats);
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project()
.engine
.poll_step::<true>(cx)
.map(|(result, stats)| {
let stats = stats.expect("async retry completed without stats");
(result, stats)
})
}
}
impl<S, W, P> RetryPolicy<S, W, P>
where
S: Stop,
W: Wait,
{
#[must_use]
pub fn retry_async<T, E, F, Fut>(
&self,
op: F,
) -> AsyncRetry<'_, S, W, P, (), (), (), F, NoAsyncSleep, T, E>
where
F: FnMut(RetryState) -> Fut,
Fut: Future<Output = Result<T, E>>,
{
AsyncRetryExec::new(
self,
ExecutionHooks::new(),
op,
NoAsyncSleep,
ElapsedTracker::new(None),
)
}
}