ClientConnectionBackoffTests.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. * Copyright 2019, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Foundation
  17. import GRPC
  18. import NIO
  19. import XCTest
  20. class ConnectivityStateCollectionDelegate: ConnectivityStateDelegate {
  21. var states: [ConnectivityState] = []
  22. func clearStates() -> [ConnectivityState] {
  23. defer {
  24. self.states = []
  25. }
  26. return self.states
  27. }
  28. var idleExpectation: XCTestExpectation?
  29. var connectingExpectation: XCTestExpectation?
  30. var readyExpectation: XCTestExpectation?
  31. var transientFailureExpectation: XCTestExpectation?
  32. var shutdownExpectation: XCTestExpectation?
  33. init(
  34. idle: XCTestExpectation? = nil,
  35. connecting: XCTestExpectation? = nil,
  36. ready: XCTestExpectation? = nil,
  37. transientFailure: XCTestExpectation? = nil,
  38. shutdown: XCTestExpectation? = nil
  39. ) {
  40. self.idleExpectation = idle
  41. self.connectingExpectation = connecting
  42. self.readyExpectation = ready
  43. self.transientFailureExpectation = transientFailure
  44. self.shutdownExpectation = shutdown
  45. }
  46. func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
  47. self.states.append(newState)
  48. switch newState {
  49. case .idle:
  50. self.idleExpectation?.fulfill()
  51. case .connecting:
  52. self.connectingExpectation?.fulfill()
  53. case .ready:
  54. self.readyExpectation?.fulfill()
  55. case .transientFailure:
  56. self.transientFailureExpectation?.fulfill()
  57. case .shutdown:
  58. self.shutdownExpectation?.fulfill()
  59. }
  60. }
  61. }
  62. class ClientConnectionBackoffTests: GRPCTestCase {
  63. let port = 8080
  64. var client: ClientConnection!
  65. var server: EventLoopFuture<Server>!
  66. var serverGroup: EventLoopGroup!
  67. var clientGroup: EventLoopGroup!
  68. var stateDelegate = ConnectivityStateCollectionDelegate()
  69. override func setUp() {
  70. self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  71. self.clientGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  72. }
  73. override func tearDown() {
  74. if let server = self.server {
  75. XCTAssertNoThrow(try server.flatMap { $0.channel.close() }.wait())
  76. }
  77. XCTAssertNoThrow(try? self.serverGroup.syncShutdownGracefully())
  78. self.server = nil
  79. self.serverGroup = nil
  80. // We don't always expect a client to be closed cleanly, since in some cases we deliberately
  81. // timeout the connection.
  82. try? self.client.close().wait()
  83. XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully())
  84. self.client = nil
  85. self.clientGroup = nil
  86. }
  87. func makeServer() -> EventLoopFuture<Server> {
  88. let configuration = Server.Configuration(
  89. target: .hostAndPort("localhost", self.port),
  90. eventLoopGroup: self.serverGroup,
  91. serviceProviders: [EchoProvider()])
  92. return Server.start(configuration: configuration)
  93. }
  94. func makeClientConfiguration() -> ClientConnection.Configuration {
  95. return .init(
  96. target: .hostAndPort("localhost", self.port),
  97. eventLoopGroup: self.clientGroup,
  98. connectivityStateDelegate: self.stateDelegate,
  99. connectionBackoff: ConnectionBackoff(maximumBackoff: 0.1))
  100. }
  101. func makeClientConnection(
  102. _ configuration: ClientConnection.Configuration
  103. ) -> ClientConnection {
  104. return ClientConnection(configuration: configuration)
  105. }
  106. func testClientConnectionFailsWithNoBackoff() throws {
  107. var configuration = self.makeClientConfiguration()
  108. configuration.connectionBackoff = nil
  109. let connectionShutdown = self.expectation(description: "client shutdown")
  110. self.stateDelegate.shutdownExpectation = connectionShutdown
  111. self.client = self.makeClientConnection(configuration)
  112. self.wait(for: [connectionShutdown], timeout: 1.0)
  113. XCTAssertEqual(self.stateDelegate.states, [.connecting, .shutdown])
  114. }
  115. func testClientEventuallyConnects() throws {
  116. let transientFailure = self.expectation(description: "connection transientFailure")
  117. let connectionReady = self.expectation(description: "connection ready")
  118. self.stateDelegate.transientFailureExpectation = transientFailure
  119. self.stateDelegate.readyExpectation = connectionReady
  120. // Start the client first.
  121. self.client = self.makeClientConnection(self.makeClientConfiguration())
  122. self.wait(for: [transientFailure], timeout: 1.0)
  123. self.server = self.makeServer()
  124. let serverStarted = self.expectation(description: "server started")
  125. self.server.assertSuccess(fulfill: serverStarted)
  126. self.wait(for: [serverStarted, connectionReady], timeout: 2.0, enforceOrder: true)
  127. XCTAssertEqual(self.stateDelegate.states, [.connecting, .transientFailure, .connecting, .ready])
  128. }
  129. func testClientEventuallyTimesOut() throws {
  130. let connectionShutdown = self.expectation(description: "connection shutdown")
  131. self.stateDelegate.shutdownExpectation = connectionShutdown
  132. self.client = self.makeClientConnection(self.makeClientConfiguration())
  133. self.wait(for: [connectionShutdown], timeout: 1.0)
  134. XCTAssertEqual(self.stateDelegate.states, [.connecting, .transientFailure, .connecting, .shutdown])
  135. }
  136. func testClientReconnectsAutomatically() throws {
  137. self.server = self.makeServer()
  138. let server = try self.server.wait()
  139. var configuration = self.makeClientConfiguration()
  140. configuration.connectionBackoff!.maximumBackoff = 2.0
  141. let connectionReady = self.expectation(description: "connection ready")
  142. let transientFailure = self.expectation(description: "connection transientFailure")
  143. self.stateDelegate.readyExpectation = connectionReady
  144. self.stateDelegate.transientFailureExpectation = transientFailure
  145. self.client = self.makeClientConnection(configuration)
  146. // Once the connection is ready we can kill the server.
  147. self.wait(for: [connectionReady], timeout: 1.0)
  148. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .ready])
  149. try server.close().wait()
  150. try self.serverGroup.syncShutdownGracefully()
  151. self.server = nil
  152. self.serverGroup = nil
  153. self.wait(for: [transientFailure], timeout: 1.0)
  154. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .transientFailure])
  155. // Replace the ready expectation (since it's already been fulfilled).
  156. let reconnectionReady = self.expectation(description: "(re)connection ready")
  157. self.stateDelegate.readyExpectation = reconnectionReady
  158. let echo = Echo_EchoServiceClient(connection: self.client)
  159. // This should succeed once we get a connection again.
  160. let get = echo.get(.with { $0.text = "hello" })
  161. // Start a new server.
  162. self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  163. self.server = self.makeServer()
  164. self.wait(for: [reconnectionReady], timeout: 2.0)
  165. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .ready])
  166. // The call should be able to succeed now.
  167. XCTAssertEqual(try get.status.map { $0.code }.wait(), .ok)
  168. try self.client.close().wait()
  169. XCTAssertEqual(self.stateDelegate.clearStates(), [.shutdown])
  170. }
  171. }