Skip to content

Instantly share code, notes, and snippets.

@BigZaphod
Created August 11, 2022 21:18
Show Gist options
  • Save BigZaphod/50ccb254a6e7047ff5693a4381a3be37 to your computer and use it in GitHub Desktop.
Save BigZaphod/50ccb254a6e7047ff5693a4381a3be37 to your computer and use it in GitHub Desktop.
//
// Created by Sean Heber on 8/11/22.
//
import Foundation
enum ExponentialBackoffError : Error {
case retryLimitExceeded
}
/// Runs the `operation` until it succeeds.
/// Success here is defined as not throwing, so if `operation` throws any errors, this function swallows them, waits a bit, and runs the `operation` again until either we reach the maximum attempts allowed or the task is cancelled.
func retryWithExponentialBackoff<Result>(base: Double = 0.25, maxInterval: Double = 60, maxAttempts: Int? = nil, operation: () async throws -> Result) async throws -> Result {
var attempt = 0
while maxAttempts == nil || attempt < maxAttempts! {
try Task.checkCancellation()
do {
return try await operation()
} catch {
// errors from the operation are ignored!
}
// This uses an exponential backoff with jitter (hence the randomness).
let sleep = base * Double(pow(Double(2), Double(attempt)))
let seconds = Double.random(in: 0...min(maxInterval, sleep))
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
attempt += 1
}
throw ExponentialBackoffError.retryLimitExceeded
}
@BigZaphod
Copy link
Author

And of course the onFailure function could also be made async there, too, and who knows what crazy possibilities that might unlock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment