use crate::valtron::{Ctx, TaskIterator, TaskStatus};
use std::time::Duration;
#[cfg(feature = "std")]
use std::time::Instant;
#[cfg(feature = "std")]
pub struct TimeoutTask<T>
where
T: TaskIterator,
{
inner: T,
timeout: Duration,
started_at: Option<Instant>,
timed_out: Option<()>,
}
#[cfg(feature = "std")]
impl<T> TimeoutTask<T>
where
T: TaskIterator,
{
pub fn new(inner: T, timeout: Duration) -> Self {
Self {
inner,
timeout,
started_at: None,
timed_out: None,
}
}
}
#[cfg(feature = "std")]
impl<T> TaskIterator for TimeoutTask<T>
where
T: TaskIterator,
{
type Ready = T::Ready;
type Pending = T::Pending;
fn next_status(&mut self, ctx: Ctx) -> Option<TaskStatus<Self::Ready, Self::Pending>> {
if self.timed_out.is_some() {
return None; }
if self.started_at.is_none() {
self.started_at = Some(Instant::now());
}
if self.started_at.unwrap().elapsed() > self.timeout {
tracing::warn!("Task timed out after {:?}", self.timeout);
return None;
}
self.inner.next_status(ctx).map(|status| match status {
TaskStatus::Pending(p) => TaskStatus::Pending(p),
TaskStatus::Ready(r) => TaskStatus::Ready(r),
TaskStatus::Delayed(d) => TaskStatus::Delayed(d),
TaskStatus::Spawn(a) => TaskStatus::Spawn(a),
TaskStatus::Init => TaskStatus::Init,
TaskStatus::Ignore => TaskStatus::Ignore,
TaskStatus::Wait => TaskStatus::Wait,
TaskStatus::Spread(items) => TaskStatus::Spread(items),
TaskStatus::Depends(signal) => TaskStatus::Depends(signal),
})
}
}
pub struct PollLimitTask<T>
where
T: TaskIterator,
{
inner: T,
max_polls: usize,
current_polls: usize,
}
impl<T> PollLimitTask<T>
where
T: TaskIterator,
{
pub fn new(inner: T, max_polls: usize) -> Self {
Self {
inner,
max_polls,
current_polls: 0,
}
}
}
impl<T> TaskIterator for PollLimitTask<T>
where
T: TaskIterator,
{
type Ready = T::Ready;
type Pending = T::Pending;
fn next_status(&mut self, ctx: Ctx) -> Option<TaskStatus<Self::Ready, Self::Pending>> {
if self.current_polls >= self.max_polls {
tracing::warn!("Task exceeded poll limit of {}", self.max_polls);
return None;
}
self.current_polls += 1;
self.inner.next_status(ctx)
}
}
pub trait RetryDecider<T> {
fn should_retry(&self, result: &T, attempt: u32) -> bool;
}
pub struct AlwaysRetry {
pub max_attempts: u32,
}
impl<T> RetryDecider<T> for AlwaysRetry {
fn should_retry(&self, _result: &T, attempt: u32) -> bool {
attempt < self.max_attempts
}
}
#[derive(Debug, Clone)]
pub enum BackoffStrategy {
Fixed(Duration),
Exponential { base: Duration, multiplier: f64 },
Linear { base: Duration, increment: Duration },
}
impl BackoffStrategy {
#[must_use]
pub fn next_delay(&self, attempt: u32, max_delay: Duration) -> Duration {
let next = match self {
Self::Fixed(d) => *d,
Self::Exponential { base, multiplier } => {
Duration::from_secs_f64(base.as_secs_f64() * multiplier.powi(attempt as i32))
}
Self::Linear { base, increment } => *base + (*increment * attempt),
};
next.min(max_delay)
}
}
pub struct BackoffTask<T>
where
T: TaskIterator,
{
inner: T,
strategy: BackoffStrategy,
max_delay: Duration,
current_attempt: u32,
in_delay: Option<Duration>,
}
impl<T> BackoffTask<T>
where
T: TaskIterator,
{
pub fn new(inner: T, strategy: BackoffStrategy, max_delay: Duration) -> Self {
Self {
inner,
strategy,
max_delay,
current_attempt: 0,
in_delay: None,
}
}
}
impl<T> TaskIterator for BackoffTask<T>
where
T: TaskIterator,
{
type Ready = T::Ready;
type Pending = T::Pending;
fn next_status(&mut self, ctx: Ctx) -> Option<TaskStatus<Self::Ready, Self::Pending>> {
if let Some(delay) = self.in_delay.take() {
return Some(TaskStatus::Delayed(delay));
}
match self.inner.next_status(ctx) {
Some(TaskStatus::Ready(result)) => {
self.current_attempt += 1;
let delay = self
.strategy
.next_delay(self.current_attempt, self.max_delay);
self.in_delay = Some(delay);
Some(TaskStatus::Ready(result))
}
other => other,
}
}
}