GRPCKeepaliveTests.swift 3.4 KB

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