How to do exponential backoff with Futures?

I can't seem to find any crates that allow me to retry a Future with exponential backoff. I would have thought that would be a pretty widely used thing, so I'm surprised that I am running into so much trouble with this. Am I misunderstanding something?

  • There is a two year old issue in reqwest that is asking for adding retries.
  • Other HTTP clients also don't seem to implement any retry policies.
  • exponential_backoff creates an Iterator, but I can't get it to work with Futures. It doesn't seem to be thread safe and when I try to use it in a for loop like in the example, but using await inside, I get std::rc::Rc<std::cell::UnsafeCell<rand::rngs::adapter::reseeding::ReseedingRng<rand::prng::hc128::Hc128Core, rand::rngs::entropy::EntropyRng>>> cannot be sent between threads safely and a massive wall of unreadable alien-like errors.
  • What looks like the most popular backoff crate only accepts functions which return Result, so you can't use async with it. There is an open issue for this but the dev doesn't seem to be active anymore and hasn't responded.
  • tokio-retry looks like it also doesn't accept async functions / Futures, but instead accepts blocking functions which will then be turned into a Future. It also looks like it might have been abandoned because there are multiple PRs to upgrade it, include to use std::future::Future, which have no response.

The list goes on. Every time I try a crate for this I run into some issue.

Am I missing something? I thought that exponential backoff with a jitter is not something you just want to quickly implement yourself, and with Rust being so focused on concurrency / resiliency I would have expected some kind of prominent crate which handles this for async functions.

That's because with async/await it's easy to make it with a simple loop:

let mut retries = 3;
let mut wait = 1;
let res = loop {
    match try_it() {       
       Err(_) if retries > 0 {
           retries -= 1;           
           delay(Duration::from_secs(wait)).await;
           wait *= 2;
       },
       res => break res,
    }
};
3 Likes

And is a jitter something simple enough that everyone usually hand implements it too?

Yeah, you could add random delay each time.

With a bit of generics (FnMut() -> F where F: Future<Output=Result<…>>) this can be turned into a reusable function.

Okay, I'll give that a shot. Thanks!

futures-retry allows you to retry futures and streams. It allows to specify a custom backoff strategy as well.

1 Like