Merge pull request #1149 from cobrowseio/development

Add configurable exponential backoff
This commit is contained in:
Erik Little 2019-01-14 14:28:27 -05:00 committed by GitHub
commit 4c98c24047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 5 deletions

View File

@ -1,3 +1,7 @@
# v14.0.0
- Add exponential backoff for reconnects, with `reconnectWaitMax` and `randomizationFactor` options [#1149](https://github.com/socketio/socket.io-client-swift/pull/1149)
# v13.4.0
- Add emits with write completion handlers. [#1096](https://github.com/socketio/socket.io-client-swift/issues/1096)
@ -69,4 +73,3 @@ Important API changes
- Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
- Makes the framework a single target.
- Updates Starscream to 3.0

View File

@ -75,8 +75,14 @@ public enum SocketIOClientOption : ClientOption {
/// The number of times to try and reconnect before giving up. Pass `-1` to [never give up](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
case reconnectAttempts(Int)
/// The number of seconds to wait before reconnect attempts.
/// The minimum number of seconds to wait before reconnect attempts.
case reconnectWait(Int)
/// The maximum number of seconds to wait before reconnect attempts.
case reconnectWaitMax(Int)
/// The randomization factor for calculating reconnect jitter.
case randomizationFactor(Double)
/// Set `true` if your server is using secure transports.
case secure(Bool)
@ -125,6 +131,10 @@ public enum SocketIOClientOption : ClientOption {
description = "reconnectAttempts"
case .reconnectWait:
description = "reconnectWait"
case .reconnectWaitMax:
description = "reconnectWaitMax"
case .randomizationFactor:
description = "randomizationFactor"
case .secure:
description = "secure"
case .selfSigned:
@ -170,6 +180,10 @@ public enum SocketIOClientOption : ClientOption {
value = attempts
case let .reconnectWait(wait):
value = wait
case let .reconnectWaitMax(wait):
value = wait
case let .randomizationFactor(factor):
value = factor
case let .secure(secure):
value = secure
case let .security(security):

View File

@ -97,9 +97,15 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
/// If `true`, this client will try and reconnect on any disconnects.
public var reconnects = true
/// The number of seconds to wait before attempting to reconnect.
/// The minimum number of seconds to wait before attempting to reconnect.
public var reconnectWait = 10
/// The maximum number of seconds to wait before attempting to reconnect.
public var reconnectWaitMax = 30
/// The randomization factor for calculating reconnect jitter.
public var randomizationFactor = 0.5
/// The status of this manager.
public private(set) var status: SocketIOStatus = .notConnected {
didSet {
@ -474,7 +480,21 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
currentReconnectAttempt += 1
connect()
handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(reconnectWait), execute: _tryReconnect)
let interval = reconnectInterval(attempts: currentReconnectAttempt)
DefaultSocketLogger.Logger.log("Scheduling reconnect in \(interval)s", type: SocketManager.logType)
handleQueue.asyncAfter(deadline: DispatchTime.now() + interval, execute: _tryReconnect)
}
func reconnectInterval(attempts: Int) -> Double {
// apply exponential factor
let backoffFactor = pow(1.5, attempts)
let interval = Double(reconnectWait) * Double(truncating: backoffFactor as NSNumber)
// add in a random factor smooth thundering herds
let rand = Double.random(in: 0 ..< 1)
let randomFactor = rand * randomizationFactor * Double(truncating: interval as NSNumber)
// add in random factor, and clamp to min and max values
let combined = interval + randomFactor
return Double(fmax(Double(reconnectWait), fmin(combined, Double(reconnectWaitMax))))
}
/// Sets manager specific configs.
@ -493,6 +513,10 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
self.reconnectAttempts = attempts
case let .reconnectWait(wait):
reconnectWait = abs(wait)
case let .reconnectWaitMax(wait):
reconnectWaitMax = abs(wait)
case let .randomizationFactor(factor):
randomizationFactor = factor
case let .log(log):
DefaultSocketLogger.Logger.log = log
case let .logger(logger):

View File

@ -69,8 +69,14 @@ public protocol SocketManagerSpec : AnyObject, SocketEngineClient {
/// If `true`, this manager will try and reconnect on any disconnects.
var reconnects: Bool { get set }
/// The number of seconds to wait before attempting to reconnect.
/// The minimum number of seconds to wait before attempting to reconnect.
var reconnectWait: Int { get set }
/// The maximum number of seconds to wait before attempting to reconnect.
var reconnectWaitMax: Int { get set }
/// The randomization factor for calculating reconnect jitter.
var randomizationFactor: Double { get set }
/// The URL of the socket.io server.
var socketURL: URL { get }

View File

@ -71,6 +71,10 @@ extension Dictionary where Key == String, Value == Any {
return .reconnectAttempts(attempts)
case let ("reconnectWait", wait as Int):
return .reconnectWait(wait)
case let ("reconnectWaitMax", wait as Int):
return .reconnectWaitMax(wait)
case let ("randomizationFactor", factor as Double):
return .randomizationFactor(factor)
case let ("secure", secure as Bool):
return .secure(secure)
case let ("security", security as SSLSecurity):

View File

@ -15,6 +15,8 @@ class SocketMangerTest : XCTestCase {
XCTAssertEqual(manager.handleQueue, DispatchQueue.main)
XCTAssertTrue(manager.reconnects)
XCTAssertEqual(manager.reconnectWait, 10)
XCTAssertEqual(manager.reconnectWaitMax, 30)
XCTAssertEqual(manager.randomizationFactor, 0.5)
XCTAssertEqual(manager.status, .notConnected)
}
@ -27,6 +29,21 @@ class SocketMangerTest : XCTestCase {
XCTAssertEqual(manager.config.first!, .secure(true))
}
func testBackoffIntervalCalulation() {
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWaitMax))
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 0), 15)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 1), 22.5)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 2), 33.75)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 50), Double(manager.reconnectWaitMax))
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWaitMax))
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWait))
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 0), Double(manager.reconnectWait))
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 1), 15)
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 2), 22.5)
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWait))
}
func testManagerCallsConnect() {
setUpSockets()
@ -90,6 +107,8 @@ class SocketMangerTest : XCTestCase {
.forceNew(true),
.reconnects(false),
.reconnectWait(5),
.reconnectWaitMax(5),
.randomizationFactor(0.7),
.reconnectAttempts(5)
])
@ -97,6 +116,8 @@ class SocketMangerTest : XCTestCase {
XCTAssertTrue(manager.forceNew)
XCTAssertFalse(manager.reconnects)
XCTAssertEqual(manager.reconnectWait, 5)
XCTAssertEqual(manager.reconnectWaitMax, 5)
XCTAssertEqual(manager.randomizationFactor, 0.7)
XCTAssertEqual(manager.reconnectAttempts, 5)
}

View File

@ -34,6 +34,8 @@
XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue());
XCTAssertTrue(self.manager.reconnects);
XCTAssertEqual(self.manager.reconnectWait, 10);
XCTAssertEqual(self.manager.reconnectWaitMax, 30);
XCTAssertEqual(self.manager.randomizationFactor, 0.5);
XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected);
}