Merge pull request #1149 from cobrowseio/development
Add configurable exponential backoff
This commit is contained in:
commit
4c98c24047
@ -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
|
# v13.4.0
|
||||||
|
|
||||||
- Add emits with write completion handlers. [#1096](https://github.com/socketio/socket.io-client-swift/issues/1096)
|
- 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.
|
- Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
|
||||||
- Makes the framework a single target.
|
- Makes the framework a single target.
|
||||||
- Updates Starscream to 3.0
|
- Updates Starscream to 3.0
|
||||||
|
|
||||||
|
|||||||
@ -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).
|
/// 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)
|
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)
|
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.
|
/// Set `true` if your server is using secure transports.
|
||||||
case secure(Bool)
|
case secure(Bool)
|
||||||
@ -125,6 +131,10 @@ public enum SocketIOClientOption : ClientOption {
|
|||||||
description = "reconnectAttempts"
|
description = "reconnectAttempts"
|
||||||
case .reconnectWait:
|
case .reconnectWait:
|
||||||
description = "reconnectWait"
|
description = "reconnectWait"
|
||||||
|
case .reconnectWaitMax:
|
||||||
|
description = "reconnectWaitMax"
|
||||||
|
case .randomizationFactor:
|
||||||
|
description = "randomizationFactor"
|
||||||
case .secure:
|
case .secure:
|
||||||
description = "secure"
|
description = "secure"
|
||||||
case .selfSigned:
|
case .selfSigned:
|
||||||
@ -170,6 +180,10 @@ public enum SocketIOClientOption : ClientOption {
|
|||||||
value = attempts
|
value = attempts
|
||||||
case let .reconnectWait(wait):
|
case let .reconnectWait(wait):
|
||||||
value = wait
|
value = wait
|
||||||
|
case let .reconnectWaitMax(wait):
|
||||||
|
value = wait
|
||||||
|
case let .randomizationFactor(factor):
|
||||||
|
value = factor
|
||||||
case let .secure(secure):
|
case let .secure(secure):
|
||||||
value = secure
|
value = secure
|
||||||
case let .security(security):
|
case let .security(security):
|
||||||
|
|||||||
@ -97,9 +97,15 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
|
|||||||
/// If `true`, this client will try and reconnect on any disconnects.
|
/// If `true`, this client will try and reconnect on any disconnects.
|
||||||
public var reconnects = true
|
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
|
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.
|
/// The status of this manager.
|
||||||
public private(set) var status: SocketIOStatus = .notConnected {
|
public private(set) var status: SocketIOStatus = .notConnected {
|
||||||
didSet {
|
didSet {
|
||||||
@ -474,7 +480,21 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
|
|||||||
currentReconnectAttempt += 1
|
currentReconnectAttempt += 1
|
||||||
connect()
|
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.
|
/// Sets manager specific configs.
|
||||||
@ -493,6 +513,10 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
|
|||||||
self.reconnectAttempts = attempts
|
self.reconnectAttempts = attempts
|
||||||
case let .reconnectWait(wait):
|
case let .reconnectWait(wait):
|
||||||
reconnectWait = abs(wait)
|
reconnectWait = abs(wait)
|
||||||
|
case let .reconnectWaitMax(wait):
|
||||||
|
reconnectWaitMax = abs(wait)
|
||||||
|
case let .randomizationFactor(factor):
|
||||||
|
randomizationFactor = factor
|
||||||
case let .log(log):
|
case let .log(log):
|
||||||
DefaultSocketLogger.Logger.log = log
|
DefaultSocketLogger.Logger.log = log
|
||||||
case let .logger(logger):
|
case let .logger(logger):
|
||||||
|
|||||||
@ -69,8 +69,14 @@ public protocol SocketManagerSpec : AnyObject, SocketEngineClient {
|
|||||||
/// If `true`, this manager will try and reconnect on any disconnects.
|
/// If `true`, this manager will try and reconnect on any disconnects.
|
||||||
var reconnects: Bool { get set }
|
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 }
|
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.
|
/// The URL of the socket.io server.
|
||||||
var socketURL: URL { get }
|
var socketURL: URL { get }
|
||||||
|
|||||||
@ -71,6 +71,10 @@ extension Dictionary where Key == String, Value == Any {
|
|||||||
return .reconnectAttempts(attempts)
|
return .reconnectAttempts(attempts)
|
||||||
case let ("reconnectWait", wait as Int):
|
case let ("reconnectWait", wait as Int):
|
||||||
return .reconnectWait(wait)
|
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):
|
case let ("secure", secure as Bool):
|
||||||
return .secure(secure)
|
return .secure(secure)
|
||||||
case let ("security", security as SSLSecurity):
|
case let ("security", security as SSLSecurity):
|
||||||
|
|||||||
@ -15,6 +15,8 @@ class SocketMangerTest : XCTestCase {
|
|||||||
XCTAssertEqual(manager.handleQueue, DispatchQueue.main)
|
XCTAssertEqual(manager.handleQueue, DispatchQueue.main)
|
||||||
XCTAssertTrue(manager.reconnects)
|
XCTAssertTrue(manager.reconnects)
|
||||||
XCTAssertEqual(manager.reconnectWait, 10)
|
XCTAssertEqual(manager.reconnectWait, 10)
|
||||||
|
XCTAssertEqual(manager.reconnectWaitMax, 30)
|
||||||
|
XCTAssertEqual(manager.randomizationFactor, 0.5)
|
||||||
XCTAssertEqual(manager.status, .notConnected)
|
XCTAssertEqual(manager.status, .notConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +29,21 @@ class SocketMangerTest : XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(manager.config.first!, .secure(true))
|
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() {
|
func testManagerCallsConnect() {
|
||||||
setUpSockets()
|
setUpSockets()
|
||||||
@ -90,6 +107,8 @@ class SocketMangerTest : XCTestCase {
|
|||||||
.forceNew(true),
|
.forceNew(true),
|
||||||
.reconnects(false),
|
.reconnects(false),
|
||||||
.reconnectWait(5),
|
.reconnectWait(5),
|
||||||
|
.reconnectWaitMax(5),
|
||||||
|
.randomizationFactor(0.7),
|
||||||
.reconnectAttempts(5)
|
.reconnectAttempts(5)
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -97,6 +116,8 @@ class SocketMangerTest : XCTestCase {
|
|||||||
XCTAssertTrue(manager.forceNew)
|
XCTAssertTrue(manager.forceNew)
|
||||||
XCTAssertFalse(manager.reconnects)
|
XCTAssertFalse(manager.reconnects)
|
||||||
XCTAssertEqual(manager.reconnectWait, 5)
|
XCTAssertEqual(manager.reconnectWait, 5)
|
||||||
|
XCTAssertEqual(manager.reconnectWaitMax, 5)
|
||||||
|
XCTAssertEqual(manager.randomizationFactor, 0.7)
|
||||||
XCTAssertEqual(manager.reconnectAttempts, 5)
|
XCTAssertEqual(manager.reconnectAttempts, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@
|
|||||||
XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue());
|
XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue());
|
||||||
XCTAssertTrue(self.manager.reconnects);
|
XCTAssertTrue(self.manager.reconnects);
|
||||||
XCTAssertEqual(self.manager.reconnectWait, 10);
|
XCTAssertEqual(self.manager.reconnectWait, 10);
|
||||||
|
XCTAssertEqual(self.manager.reconnectWaitMax, 30);
|
||||||
|
XCTAssertEqual(self.manager.randomizationFactor, 0.5);
|
||||||
XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected);
|
XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user