Skip to content

Instantly share code, notes, and snippets.

@bencochran
Created March 17, 2016 07:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bencochran/6fff5bd6de8d2de4261e to your computer and use it in GitHub Desktop.
Save bencochran/6fff5bd6de8d2de4261e to your computer and use it in GitHub Desktop.
import Foundation
import ReactiveCocoa
//: # Backoff
//: ## Retry with backoff operator
extension SignalProducerType {
func retryWithBackoff<S: SequenceType where S.Generator.Element == NSTimeInterval>(strategy: S) -> SignalProducer<Value, Error> {
var generator = strategy.generate()
guard let timeout = generator.next() else {
return producer
}
return flatMapError { _ -> SignalProducer<Value, Error> in
return timer(timeout, onScheduler: QueueScheduler.mainQueueScheduler)
.take(1)
.promoteErrors(Error)
.then(self.retryWithBackoff(GeneratorSequence(generator)))
}
}
}
//: ## Backoff strategy sequences
struct ExponentialSequence: SequenceType {
func generate() -> AnyGenerator<NSTimeInterval> {
var i = -1
return AnyGenerator {
i += 1
return pow(2, NSTimeInterval(i))
}
}
}
struct FibonnaciSequence: SequenceType {
func generate() -> AnyGenerator<NSTimeInterval> {
var a: NSTimeInterval = 0
var b: NSTimeInterval = 1
return AnyGenerator {
let next = b + a
a = b
b = next
return a
}
}
}
struct JitterSequence<S: SequenceType where S.Generator.Element == NSTimeInterval>: SequenceType {
let coefficient: Double
let underlyingSequence: S
init(_ underlyingSequence: S, coefficient: Double = 0.33) {
self.underlyingSequence = underlyingSequence
self.coefficient = coefficient
}
func generate() -> AnyGenerator<NSTimeInterval> {
var underlyingGenerator = underlyingSequence.generate()
return AnyGenerator {
return underlyingGenerator.next().map(self.jitter)
}
}
private func jitter(interval: NSTimeInterval) -> NSTimeInterval {
let millisecondInterval = interval * 1000
let millisecondJitterRange = UInt32(millisecondInterval * coefficient)
let millisecondJitter = NSTimeInterval(millisecondJitterRange) - NSTimeInterval(arc4random_uniform(millisecondJitterRange * 2))
return (millisecondInterval + millisecondJitter) / 1000
}
}
//: ## Demo
enum MyError: ErrorType {
case Foo
}
func failProducer(logString logString: String, numFails: Int = .max) -> SignalProducer<(), MyError> {
var numLeft = numFails
return SignalProducer<(), MyError> { observer, _ in
NSLog(logString)
if numLeft == 0 {
observer.sendNext(())
observer.sendCompleted()
} else {
observer.sendFailed(MyError.Foo)
}
numLeft -= 1
}
}
failProducer(logString: "Starting Linear", numFails: 3)
.retryWithBackoff(Array(count: 5, repeatedValue: 0.5))
.start({ NSLog("Linear: \($0)") })
let exponential = ExponentialSequence()
let jitterExponential = JitterSequence(exponential)
failProducer(logString: "Starting Exponential", numFails: 3)
.retryWithBackoff(jitterExponential)
.start({ NSLog("Exponential: \($0)") })
let fibonnaci = FibonnaciSequence()
let jitterFibonnaci = JitterSequence(fibonnaci)
failProducer(logString: "Starting Fibonacci")
.retryWithBackoff(jitterFibonnaci.prefix(10))
.start({ NSLog("Fibonacci: \($0)") })
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
@eabadjiev
Copy link

Note that this code will fail to compile with Swift 2.3 in Release due to this https://bugs.swift.org/browse/SR-3539

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