-
-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvements to the Retry Layer #399
Comments
Technically we have first class support for anything from tower. |
In case somebody is looking for a working solution for apalis v0.5 that they can just copy-paste and easily modify: use anyhow::Result;
use apalis::prelude::*;
use std::time::Duration;
use tokio::time::{sleep, Sleep};
use tower::retry::Policy;
type Req<T> = Request<T>;
type Err = Error;
#[derive(Clone, Debug)]
pub struct BackoffRetryPolicy {
pub retries: usize,
pub initial_backoff: Duration,
pub multiplier: f64,
pub max_backoff: Duration,
}
impl Default for BackoffRetryPolicy {
fn default() -> Self {
Self {
retries: 25,
initial_backoff: Duration::from_millis(1000),
multiplier: 1.5,
max_backoff: Duration::from_secs(60),
}
}
}
impl BackoffRetryPolicy {
fn backoff_duration(&self, attempt: usize) -> Duration {
let backoff = self.initial_backoff.as_millis() as f64 * self.multiplier.powi(attempt as i32);
Duration::from_millis(backoff.min(self.max_backoff.as_millis() as f64) as u64)
}
}
impl<T, Res> Policy<Req<T>, Res, Err> for BackoffRetryPolicy
where
T: Clone,
{
type Future = Sleep;
fn retry(&mut self, req: &mut Req<T>, result: &mut Result<Res, Err>) -> Option<Self::Future> {
let ctx = req.get::<Attempt>().cloned().unwrap_or_default();
match result {
Ok(_) => None,
Err(_) if (self.retries - ctx.current() > 0) => {
let backoff_duration = self.backoff_duration(ctx.current());
Some(sleep(backoff_duration))
}
Err(_) => None,
}
}
fn clone_request(&mut self, req: &Req<T>) -> Option<Req<T>> {
let mut req = req.clone();
let value = req
.get::<Attempt>()
.cloned()
.map(|attempt| {
attempt.increment();
attempt
})
.unwrap_or_default();
req.insert(value);
Some(req)
}
} and then: // ...
.layer(RetryLayer::new(BackoffRetryPolicy::default())
// or
.layer(RetryLayer::new(BackoffRetryPolicy {
retries: 10,
initial_backoff: std::time::Duration::from_millis(1000),
multiplier: 4.0,
max_backoff: std::time::Duration::from_secs(60),
})) |
@reneklacan The example provided is outdated, it would only work for v0.5. |
@geofmureithi I updated my comment to mention v0.5 (when I get to upgrading Apalis, will make sure to add a version for v0.6 as well)
I realize that based on your first comment in this thread but I mean this one is still better than nothing Either way, thanks a lot for all the effort you are putting into Apalis. |
Yeah it's better than nothing. From your approach I got some ideas. Let me try something and see if it works. |
Working version of previously shared retry policy for Apalis v0.6 use anyhow::Result;
use apalis::prelude::*;
use std::time::Duration;
use tokio::time::{sleep, Sleep};
use tower::retry::Policy;
type Req<T, Ctx> = Request<T, Ctx>;
type Err = Error;
#[derive(Clone, Debug)]
pub struct BackoffRetryPolicy {
pub retries: usize,
pub initial_backoff: Duration,
pub multiplier: f64,
pub max_backoff: Duration,
}
impl Default for BackoffRetryPolicy {
fn default() -> Self {
Self {
retries: 25,
initial_backoff: Duration::from_millis(1000),
multiplier: 1.5,
max_backoff: Duration::from_secs(60),
}
}
}
impl BackoffRetryPolicy {
fn backoff_duration(&self, attempt: usize) -> Duration {
let backoff = self.initial_backoff.as_millis() as f64 * self.multiplier.powi(attempt as i32);
Duration::from_millis(backoff.min(self.max_backoff.as_millis() as f64) as u64)
}
}
impl<T, Res, Ctx> Policy<Req<T, Ctx>, Res, Err> for BackoffRetryPolicy
where
T: Clone,
Ctx: Clone,
{
type Future = Sleep;
fn retry(&mut self, req: &mut Req<T, Ctx>, result: &mut Result<Res, Err>) -> Option<Self::Future> {
let attempt = req.parts.attempt.current();
match result {
Ok(_) => None,
Err(_) if (self.retries - attempt > 0) => Some(sleep(self.backoff_duration(attempt))),
Err(_) => None,
}
}
fn clone_request(&mut self, req: &Req<T, Ctx>) -> Option<Req<T, Ctx>> {
let req = req.clone();
req.parts.attempt.increment();
Some(req)
}
} Usage is the same: // ...
.layer(RetryLayer::new(BackoffRetryPolicy::default())
// or
.layer(RetryLayer::new(BackoffRetryPolicy {
retries: 10,
initial_backoff: std::time::Duration::from_millis(1000),
multiplier: 4.0,
max_backoff: std::time::Duration::from_secs(60),
})) |
@reneklacan I would recommend using |
@geofmureithi thanks, updated and refactored my example and it feels much cleaner now |
Awesome! You can also replace |
Just another reminder, that this does not synchronize the number of attempts with the backend. |
Hi, I have seen that in the new tower v0.5 they finally added the Backoff/ExponentialBackoff policy which can be used with the retry layer.
It will be great if this library will be able to integrate this.
Thanks
The text was updated successfully, but these errors were encountered: