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
 | 
			
		||||
 | 
			
		||||
- 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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 }
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user