ClientConnectionBackoffTests.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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. // We have additional state changes during tear down, in some cases we can over-fulfill a test
  75. // expectation which causes false negatives.
  76. self.client.connectivity.delegate = nil
  77. if let server = self.server {
  78. XCTAssertNoThrow(try server.flatMap { $0.channel.close() }.wait())
  79. }
  80. XCTAssertNoThrow(try? self.serverGroup.syncShutdownGracefully())
  81. self.server = nil
  82. self.serverGroup = nil
  83. // We don't always expect a client to be closed cleanly, since in some cases we deliberately
  84. // timeout the connection.
  85. try? self.client.close().wait()
  86. XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully())
  87. self.client = nil
  88. self.clientGroup = nil
  89. }
  90. func makeServer() -> EventLoopFuture<Server> {
  91. let configuration = Server.Configuration(
  92. target: .hostAndPort("localhost", self.port),
  93. eventLoopGroup: self.serverGroup,
  94. serviceProviders: [EchoProvider()])
  95. return Server.start(configuration: configuration)
  96. }
  97. func makeClientConfiguration() -> ClientConnection.Configuration {
  98. return .init(
  99. target: .hostAndPort("localhost", self.port),
  100. eventLoopGroup: self.clientGroup,
  101. connectivityStateDelegate: self.stateDelegate,
  102. connectionBackoff: ConnectionBackoff(maximumBackoff: 0.1))
  103. }
  104. func makeClientConnection(
  105. _ configuration: ClientConnection.Configuration
  106. ) -> ClientConnection {
  107. return ClientConnection(configuration: configuration)
  108. }
  109. func testClientConnectionFailsWithNoBackoff() throws {
  110. var configuration = self.makeClientConfiguration()
  111. configuration.connectionBackoff = nil
  112. let connectionShutdown = self.expectation(description: "client shutdown")
  113. self.stateDelegate.shutdownExpectation = connectionShutdown
  114. self.client = self.makeClientConnection(configuration)
  115. self.wait(for: [connectionShutdown], timeout: 1.0)
  116. XCTAssertEqual(self.stateDelegate.states, [.connecting, .shutdown])
  117. }
  118. func testClientEventuallyConnects() throws {
  119. let transientFailure = self.expectation(description: "connection transientFailure")
  120. let connectionReady = self.expectation(description: "connection ready")
  121. self.stateDelegate.transientFailureExpectation = transientFailure
  122. self.stateDelegate.readyExpectation = connectionReady
  123. // Start the client first.
  124. self.client = self.makeClientConnection(self.makeClientConfiguration())
  125. self.wait(for: [transientFailure], timeout: 1.0)
  126. self.server = self.makeServer()
  127. let serverStarted = self.expectation(description: "server started")
  128. self.server.assertSuccess(fulfill: serverStarted)
  129. self.wait(for: [serverStarted, connectionReady], timeout: 2.0, enforceOrder: true)
  130. XCTAssertEqual(self.stateDelegate.states, [.connecting, .transientFailure, .connecting, .ready])
  131. }
  132. func testClientEventuallyTimesOut() throws {
  133. let connectionShutdown = self.expectation(description: "connection shutdown")
  134. self.stateDelegate.shutdownExpectation = connectionShutdown
  135. self.client = self.makeClientConnection(self.makeClientConfiguration())
  136. self.wait(for: [connectionShutdown], timeout: 1.0)
  137. XCTAssertEqual(self.stateDelegate.states, [.connecting, .transientFailure, .connecting, .shutdown])
  138. }
  139. func testClientReconnectsAutomatically() throws {
  140. self.server = self.makeServer()
  141. let server = try self.server.wait()
  142. var configuration = self.makeClientConfiguration()
  143. configuration.connectionBackoff!.maximumBackoff = 2.0
  144. let connectionReady = self.expectation(description: "connection ready")
  145. let transientFailure = self.expectation(description: "connection transientFailure")
  146. self.stateDelegate.readyExpectation = connectionReady
  147. self.stateDelegate.transientFailureExpectation = transientFailure
  148. self.client = self.makeClientConnection(configuration)
  149. // Once the connection is ready we can kill the server.
  150. self.wait(for: [connectionReady], timeout: 1.0)
  151. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .ready])
  152. try server.close().wait()
  153. try self.serverGroup.syncShutdownGracefully()
  154. self.server = nil
  155. self.serverGroup = nil
  156. self.wait(for: [transientFailure], timeout: 1.0)
  157. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .transientFailure])
  158. // Replace the ready expectation (since it's already been fulfilled).
  159. let reconnectionReady = self.expectation(description: "(re)connection ready")
  160. self.stateDelegate.readyExpectation = reconnectionReady
  161. let echo = Echo_EchoServiceClient(connection: self.client)
  162. // This should succeed once we get a connection again.
  163. let get = echo.get(.with { $0.text = "hello" })
  164. // Start a new server.
  165. self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  166. self.server = self.makeServer()
  167. self.wait(for: [reconnectionReady], timeout: 2.0)
  168. XCTAssertEqual(self.stateDelegate.clearStates(), [.connecting, .ready])
  169. // The call should be able to succeed now.
  170. XCTAssertEqual(try get.status.map { $0.code }.wait(), .ok)
  171. try self.client.close().wait()
  172. XCTAssertEqual(self.stateDelegate.clearStates(), [.shutdown])
  173. }
  174. }