Last active
February 27, 2020 05:53
-
-
Save a-voronov/f63726568a27b4650ac33d53ce866c19 to your computer and use it in GitHub Desktop.
Exponential Backoff Iterator + Retry SignalProducer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct ExponentialBackoffConfig { | |
let initialRetries: Int | |
let initialInterval: TimeInterval | |
let maxInterval: TimeInterval | |
let factor: TimeInterval | |
init(initialRetries: Int = 2, initialInterval: TimeInterval = 0.3, maxInterval: TimeInterval = 120, factor: TimeInterval = 1.6) { | |
precondition(initialRetries >= 0 && initialInterval >= 0 && maxInterval >= initialInterval && factor > 1) | |
self.initialRetries = initialRetries | |
self.initialInterval = initialInterval | |
self.maxInterval = maxInterval | |
self.factor = factor | |
} | |
} | |
struct ExponentialBackoffIterator: IteratorProtocol { | |
let config: ExponentialBackoffConfig | |
private var attempt: Int = -1 | |
init(config: ExponentialBackoffConfig = .init()) { | |
self.config = config | |
} | |
mutating func next() -> TimeInterval? { | |
attempt += 1 | |
guard attempt > config.initialRetries else { | |
return config.initialInterval | |
} | |
let interval = pow(config.factor, TimeInterval(attempt - config.initialRetries)) * config.initialInterval | |
return min(interval, config.maxInterval) | |
} | |
} | |
extension SignalProducer { | |
func retryWithExponentialBackoff( | |
upTo count: Int = .max, | |
while condition: @escaping (Error) -> Bool = { _ in true }, | |
using config: ExponentialBackoffConfig = .init(), | |
on scheduler: DateScheduler = QueueScheduler() | |
) -> SignalProducer<Value, Error> { | |
precondition(count >= 0) | |
guard count > 0 else { | |
return producer | |
} | |
var retries = count | |
var iterator = ExponentialBackoffIterator(config: config) | |
func retry() -> SignalProducer<Value, Error> { | |
return flatMapError { error in | |
guard let interval = iterator.next(), retries > 0 && condition(error) else { | |
return SignalProducer(error: error) | |
} | |
retries -= 1 | |
return SignalProducer.empty | |
.delay(interval, on: scheduler) | |
.concat(retry()) | |
} | |
} | |
return retry() | |
.on(value: { _ in | |
retries = count | |
iterator = ExponentialBackoffIterator(config: config) | |
}) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Iterator
Output:
SignalProducer
Output