GRPCKeepaliveTests.swift 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. /*
  2. * Copyright 2020, 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 EchoImplementation
  17. import EchoModel
  18. import NIOCore
  19. import NIOPosix
  20. import XCTest
  21. @testable import GRPC
  22. class GRPCClientKeepaliveTests: GRPCTestCase {
  23. func testKeepaliveTimeoutFiresBeforeConnectionIsReady() throws {
  24. // This test relates to https://github.com/grpc/grpc-swift/issues/949
  25. //
  26. // When a stream is created, a ping may be sent on the connection. If a ping is sent we then
  27. // schedule a task for some time in the future to close the connection (if we don't receive the
  28. // ping ack in the meantime).
  29. //
  30. // The task to close actually fires an event which is picked up by the idle handler; this will
  31. // tell the connection manager to idle the connection. However, the connection manager only
  32. // tolerates being idled from the ready state. Since we protect from idling multiple times in
  33. // the handler we must be in a state where we have connection but are not yet ready (i.e.
  34. // channel active has fired but we have not seen the initial settings frame). To be in this
  35. // state the user must be using the 'fastFailure' call start behaviour (if this is not the case
  36. // then no channel will be vended until we reach the ready state, so it would not be possible
  37. // to create the stream).
  38. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  39. defer {
  40. XCTAssertNoThrow(try group.syncShutdownGracefully())
  41. }
  42. // Setup a server.
  43. let server = try Server.insecure(group: group)
  44. .withServiceProviders([EchoProvider()])
  45. .withLogger(self.serverLogger)
  46. .bind(host: "localhost", port: 0)
  47. .wait()
  48. defer {
  49. XCTAssertNoThrow(try server.close().wait())
  50. }
  51. // Setup a connection. We'll add a handler to drop all reads, this is somewhat equivalent to
  52. // simulating bad network conditions and allows us to setup a connection and have our keepalive
  53. // timeout expire.
  54. let connection = ClientConnection.insecure(group: group)
  55. .withBackgroundActivityLogger(self.clientLogger)
  56. // See above comments for why we need this.
  57. .withCallStartBehavior(.fastFailure)
  58. .withKeepalive(.init(interval: .seconds(1), timeout: .milliseconds(100)))
  59. .withDebugChannelInitializer { channel in
  60. channel.pipeline.addHandler(ReadDroppingHandler(), position: .first)
  61. }
  62. .connect(host: "localhost", port: server.channel.localAddress!.port!)
  63. defer {
  64. XCTAssertNoThrow(try connection.close().wait())
  65. }
  66. let client = Echo_EchoNIOClient(channel: connection)
  67. let get = client.get(.with { $0.text = "Hello" })
  68. XCTAssertThrowsError(try get.response.wait())
  69. XCTAssertEqual(try get.status.map { $0.code }.wait(), .unavailable)
  70. }
  71. class ReadDroppingHandler: ChannelDuplexHandler {
  72. typealias InboundIn = Any
  73. typealias OutboundIn = Any
  74. func channelRead(context: ChannelHandlerContext, data: NIOAny) {}
  75. }
  76. }