@@ -132,5 +132,84 @@ class AlgoliaRetryStrategyTests: XCTestCase {
132132
133133 XCTAssertEqual ( newReadHosts. next ( ) ? . url. absoluteString, " algolia3.com " )
134134 }
135+
136+ /// Load test of hosts retry strategy
137+ func testLoad( ) {
138+ let expirationDelay : TimeInterval = 3
139+ let strategy = AlgoliaRetryStrategy ( hosts: [ host3, host4, host5] , hostsExpirationDelay: expirationDelay)
140+
141+ let queueCount = 100
142+ // Generate `queueCount` queues with associated count of operation for each
143+ let queuesWithOpCount = ( 1 ... queueCount)
144+ . map { ( queue: DispatchQueue ( label: " queue \( $0) " ) , operationCount: Int . random ( in: 100 ... 1000 ) ) }
145+
146+ let operationQueue = OperationQueue ( )
147+ operationQueue. maxConcurrentOperationCount = 50
148+
149+ // Expectation of completion of all the operations from all the queues
150+ let allOperationsFinishedExpectation = expectation ( description: " All operations finished " )
151+ allOperationsFinishedExpectation. expectedFulfillmentCount = queuesWithOpCount. map ( \. operationCount) . reduce ( 0 , + )
152+
153+ // Launch `operationCount` operation from each queue with randomized launch delay
154+ for (queue, operationCount) in queuesWithOpCount {
155+ for count in 1 ... operationCount {
156+ queue. asyncAfter ( deadline: . now( ) + . milliseconds( . random( in: 10 ... 500 ) ) ) {
157+ let callType : CallType = [ . read, . write] . randomElement ( ) !
158+ let operation = RetryableOperation ( retryStrategy: strategy, callType: callType)
159+ operation. name = " \( queue. label) - \( count) "
160+ operation. completionBlock = {
161+ allOperationsFinishedExpectation. fulfill ( )
162+ }
163+ operationQueue. addOperation ( operation)
164+ }
165+ }
166+ }
167+
168+ waitForExpectations ( timeout: 20 , handler: nil )
169+ }
135170
136171}
172+
173+ /// Operation imitating server responses for the purpose of testing the retry strategy
174+ class RetryableOperation : Operation {
175+
176+ let retryStrategy : RetryStrategy
177+ let callType : CallType
178+ let waitQueue = DispatchQueue ( label: " internal " )
179+
180+ init ( retryStrategy: RetryStrategy , callType: CallType ) {
181+ self . retryStrategy = retryStrategy
182+ self . callType = callType
183+ super. init ( )
184+ }
185+
186+ override var debugDescription : String {
187+ return " \( name ?? " " ) "
188+ }
189+
190+ override func main( ) {
191+ let hostsIterator = retryStrategy. retryableHosts ( for: callType)
192+
193+ while let host = hostsIterator. next ( ) {
194+ let result = getResult ( )
195+ switch retryStrategy. notify ( host: host, result: result) {
196+ case . failure, . success:
197+ break
198+ case . retry:
199+ continue
200+ }
201+ }
202+ }
203+
204+ func getResult( ) -> Result < String , Error > {
205+ return [
206+ . failure( HTTPError ( statusCode: . requestTimeout, message: nil ) ) ,
207+ . failure( URLError ( . badServerResponse) ) ,
208+ . failure( InternalError ( ) ) ,
209+ . success( " " )
210+ ] . randomElement ( ) !
211+ }
212+
213+ struct InternalError : Error { }
214+
215+ }
0 commit comments