This algorithm allows us to retry an operation, gradually increasing the time between repeats.
For example, if we’re trying to do a network call from a client, but the server is temporarily down, maybe we’ll want to wait a couple of seconds and try again. If that fails, then we’ll want to wait a bit longer before retrying, so we don’t overload the server with even more calls.
The standard Truncated Exponential Backoff algorithm looks like this:
2ˆn
, where n
is the iteration numberNote: Adding a bit of jitter is often useful because if we have a number of calls failing at exactly the same time, we don’t want the retries to be synchronized as well. In the client/server example this will help ease some of the load when the server is back online.
Let’s do this in Swift. Calculating the delay time in milliseconds:
func getDelay(for n: Int) -> Int {
let maxDelay = 300000 // 5 minutes
let delay = Int(pow(2.0, Double(n))) * 1000
let jitter = Int.random(in: 0...1000)
return min(delay + jitter, maxDelay)
}
We can then use GCD’s asyncAfter
to execute our closure on a DispatchQueue
:
func execute(on queue: DispatchQueue, retry: Int = 0, closure: @escaping () -> Void) {
let delay = getDelay(for: retry)
queue.asyncAfter(
deadline: DispatchTime.now() + .milliseconds(delay),
execute: closure)
}
GCD doesn’t guarantee that asyncAfter
will execute our closure at an exact time, only that it will be after the delay. This makes the best use of the system’s resources and for most use cases this is precisely what we want. If your use case requires accurate timing, use NSTimer
instead.