Skip to content

Commit

Permalink
Merge ca253bb into 8a12193
Browse files Browse the repository at this point in the history
  • Loading branch information
jayahariv committed Feb 19, 2021
2 parents 8a12193 + ca253bb commit e36c1b7
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 2 deletions.
63 changes: 61 additions & 2 deletions Swift/ReplicatorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,58 @@ public class ReplicatorConfiguration {
}
}

private var _maxRetries: Int = -1
/**
The maximum attempts to perform retry. The retry attempt will be reset when the replicator is
able to connect and replicate with the remote server again.
Without setting the maxRetries value, the default maxRetries of 9 times for single shot
replicators and infinite times for continuous replicators will be applied and present to users.
Settings the value to 0 will result in no retry attempt.
Setting a negative number will result in InvalidArgumentException being thrown.
*/
public var maxRetries: Int {
set(newValue) {
checkReadOnly()

guard newValue >= 0 else {
NSException(name: .invalidArgumentException,
reason: "Attempt to store negative value in maxRetries",
userInfo: nil).raise()
return
}

_maxRetries = newValue
}

get {
return _maxRetries >= 0
? _maxRetries : self.continuous
? ReplicatorConfiguration.defaultContinousMaxRetries : ReplicatorConfiguration.defaultSingleShotMaxRetries
}
}

/*
Max wait time for the next retry.
The exponential backoff for calculating the wait time will be used by default and cannot be
customized. Set the maxRetryWaitTime to zero or negative value will result in
InvalidArgumentException being thrown.
*/
public var maxRetryWaitTime: TimeInterval = 300 {
willSet(newValue) {
checkReadOnly()

guard newValue > 0 else {
NSException(name: .invalidArgumentException,
reason: "Attempt to store zero or negative value in maxRetryWaitTime",
userInfo: nil).raise()
return
}
}
}

/// Initializes a ReplicatorConfiguration's builder with the given
/// local database and the replication target.
///
Expand All @@ -213,10 +265,11 @@ public class ReplicatorConfiguration {

// MARK: Internal

private let readonly: Bool
private var readonly = false
private static let defaultContinousMaxRetries = NSInteger.max
private static let defaultSingleShotMaxRetries = 9

init(config: ReplicatorConfiguration, readonly: Bool) {
self.readonly = readonly
self.database = config.database
self.target = config.target
self.replicatorType = config.replicatorType
Expand All @@ -228,6 +281,8 @@ public class ReplicatorConfiguration {
self.documentIDs = config.documentIDs
self.conflictResolver = config.conflictResolver
self.heartbeat = config.heartbeat
self.maxRetries = config.maxRetries
self.maxRetryWaitTime = config.maxRetryWaitTime

#if os(iOS)
self.allowReplicatingInBackground = config.allowReplicatingInBackground
Expand All @@ -236,6 +291,8 @@ public class ReplicatorConfiguration {
#if COUCHBASE_ENTERPRISE
self.acceptOnlySelfSignedServerCertificate = config.acceptOnlySelfSignedServerCertificate
#endif

self.readonly = readonly
}

func checkReadOnly() {
Expand All @@ -257,6 +314,8 @@ public class ReplicatorConfiguration {
c.pushFilter = self.filter(push: true)
c.pullFilter = self.filter(push: false)
c.heartbeat = self.heartbeat
c.maxRetries = self.maxRetries
c.maxRetryWaitTime = self.maxRetryWaitTime

if let resolver = self.conflictResolver {
c.setConflictResolverUsing { (conflict) -> CBLDocument? in
Expand Down
152 changes: 152 additions & 0 deletions Swift/Tests/ReplicatorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class ReplicatorTest: CBLTestCase {
var repl: Replicator!
var timeout: TimeInterval = 10 // At least 10 to cover single-shot replicator's retry logic

// connect to an unknown-db on same machine, for the connection refused transient error.
let kConnRefusedTarget: URLEndpoint = URLEndpoint(url: URL(string: "ws://localhost:4984/unknown-db-wXBl5n3fed")!)

override func setUp() {
super.setUp()
try! openOtherDB()
Expand Down Expand Up @@ -1015,4 +1018,153 @@ class ReplicatorTest_Main: ReplicatorTest {
XCTAssertEqual(config.heartbeat, 60)
repl = nil
}

// MARK: Retry Logic

func testMaxRetryCount() {
// single shot
var config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
XCTAssertEqual(config.maxRetries, 9)

// continous
config = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: true)
XCTAssertEqual(config.maxRetries, NSInteger.max)
}

func testCustomMaxRetryCount() {
// single shot
var config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
config.maxRetries = 22
XCTAssertEqual(config.maxRetries, 22)

// continous
config = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: true)
config.maxRetries = 11
XCTAssertEqual(config.maxRetries, 11)
}

func testInvalidMaxRetry() {
let config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
func expectException() throws {
do {
try CBLTestHelper.catchException {
config.maxRetries = -1
}
} catch {
XCTAssertEqual((error as NSError).domain,
NSExceptionName.invalidArgumentException.rawValue)
throw error
}
}

XCTAssertThrowsError(try expectException())
}

func testMaxRetry(retry: Int, count: Int, continuous: Bool) {
let x = self.expectation(description: "repl finish")
let config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget,
type: .pushAndPull,
continuous: continuous)

var offlineCount = 0
if retry >= 0 {
config.maxRetries = retry
}

repl = Replicator(config: config)
repl.addChangeListener { (change) in
if change.status.activity == .offline {
offlineCount += 1
} else if change.status.activity == .stopped {
x.fulfill()
}
}

repl.start()
wait(for: [x], timeout: timeout)
XCTAssertEqual(count, offlineCount)
}

func testMaxRetry() {
testMaxRetry(retry: 0, count: 0, continuous: false)
testMaxRetry(retry: 0, count: 0, continuous: true)

testMaxRetry(retry: 1, count: 1, continuous: false)
testMaxRetry(retry: 1, count: 1, continuous: true)
}

// disbale the test, since this might take ~13mints; when testing, change the timeout to 900secs
func _testMaxRetryForSingleShot() {
testMaxRetry(retry: -1, count: 9, continuous: false)
}

// MARK: Max Retry Wait Time

func testMaxRetryWaitTime() {
// single shot
var config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
XCTAssertEqual(config.maxRetryWaitTime, 300)

// continous
config = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: true)
XCTAssertEqual(config.maxRetryWaitTime, 300)

repl = Replicator(config: config)
XCTAssertEqual(repl.config.maxRetryWaitTime, 300)
}

func testCustomMaxRetryWaitTime() {
// single shot
var config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
config.maxRetryWaitTime = 444
XCTAssertEqual(config.maxRetryWaitTime, 444)

// continous
config = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: true)
config.maxRetryWaitTime = 555
XCTAssertEqual(config.maxRetryWaitTime, 555)
}

func testInvalidMaxRetryWaitTime() {
let config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget, type: .pushAndPull, continuous: false)
func expectException(_ val: TimeInterval) throws {
do {
try CBLTestHelper.catchException {
config.maxRetryWaitTime = val
}
} catch {
XCTAssertEqual((error as NSError).domain,
NSExceptionName.invalidArgumentException.rawValue)
throw error
}
}

XCTAssertThrowsError(try expectException(0))
XCTAssertThrowsError(try expectException(-1))
}

func testMaxRetryWaitTimeOfReplicator() {
let x = self.expectation(description: "repl finish")
let config: ReplicatorConfiguration = self.config(target: kConnRefusedTarget,
type: .pushAndPull,
continuous: false)
config.maxRetries = 4
config.maxRetryWaitTime = 2

var diff: TimeInterval = 0
var time = Date()
repl = Replicator(config: config)
repl.addChangeListener { (change) in
if change.status.activity == .offline {
diff = Date().timeIntervalSince(time)
time = Date()
} else if change.status.activity == .stopped {
x.fulfill()
}
}

repl.start()
wait(for: [x], timeout: timeout)
XCTAssert(abs(diff - config.maxRetryWaitTime) < 1.0)
}
}

0 comments on commit e36c1b7

Please sign in to comment.