use crate::OperationResult;
use async_trait::async_trait;
use futures_util::{Stream, StreamExt};
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(test)]
mod test;
pub enum Status<O, E> {
Pending,
Success(O),
Failure(E),
}
impl<O, E> std::fmt::Debug for Status<O, E>
where
O: std::fmt::Debug,
E: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "Pending"),
Self::Success(o) => write!(f, "Success({:?})", o),
Self::Failure(e) => write!(f, "Failure({:?})", e),
}
}
}
#[async_trait]
pub trait RetryInjector<'a>: Sized {
type Input: Serialize + Deserialize<'a> + Clone;
type Output;
type Error;
type Id: Clone;
type Res: Into<OperationResult<Self::Output, Self::Error>>;
async fn load_pending(&mut self) -> Vec<(Self::Id, Self::Input)>;
async fn save_status(
&mut self,
id: Self::Id,
input: Self::Input,
status: Status<Self::Output, Self::Error>,
);
}
pub struct RetryHandle<Inj, Dur> {
injector: Inj,
durations: Dur,
}
impl<'a, Inj, Dur> RetryHandle<Inj, Dur>
where
Inj: RetryInjector<'a>,
Dur: IntoIterator<Item = std::time::Duration> + Clone,
{
pub fn new(injector: Inj, durations: Dur) -> Self {
Self {
injector,
durations,
}
}
pub async fn retry_pending<F>(
&mut self,
concurrency_limit: usize,
operation: &dyn Fn(Inj::Input) -> F,
) where
F: Future<Output = Inj::Res>,
{
let pending = self.injector.load_pending().await;
self.retry_stream(tokio_stream::iter(pending), concurrency_limit, operation)
.await;
}
pub async fn retry_stream<F, S>(
&mut self,
stream: S,
concurrency_limit: usize,
operation: &dyn Fn(Inj::Input) -> F,
) where
F: Future<Output = Inj::Res>,
S: Stream<Item = (Inj::Id, Inj::Input)>,
{
let handle = Arc::new(Mutex::new(self));
stream
.for_each_concurrent(concurrency_limit, |(id, input)| async {
handle.lock().await.retry(id, input, operation).await;
})
.await;
}
pub async fn retry<F>(
&mut self,
id: Inj::Id,
input: Inj::Input,
operation: &dyn Fn(Inj::Input) -> F,
) where
F: Future<Output = Inj::Res>,
{
self.injector
.save_status(id.clone(), input.clone(), Status::Pending)
.await;
let mut it = self.durations.clone().into_iter();
let res = loop {
match operation(input.clone()).await.into() {
OperationResult::Ok(res) => break Ok(res),
OperationResult::Err(e) => break Err(e),
OperationResult::Retry(e) => {
if let Some(duration) = it.next() {
tokio::time::sleep(duration).await;
} else {
break Err(e);
}
}
}
};
let status = match res {
Ok(ok) => Status::Success(ok),
Err(err) => Status::Failure(err),
};
self.injector
.save_status(id.clone(), input.clone(), status)
.await
}
}