ConnectionKeepalive.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 NIOCore
  17. /// Provides keepalive pings.
  18. ///
  19. /// The defaults are determined by the gRPC keepalive
  20. /// [documentation] (https://github.com/grpc/grpc/blob/master/doc/keepalive.md).
  21. public struct ClientConnectionKeepalive: Hashable, Sendable {
  22. private func checkInvariants(line: UInt = #line) {
  23. precondition(self.timeout < self.interval, "'timeout' must be less than 'interval'", line: line)
  24. }
  25. /// The amount of time to wait before sending a keepalive ping.
  26. public var interval: TimeAmount {
  27. didSet { self.checkInvariants() }
  28. }
  29. /// The amount of time to wait for an acknowledgment.
  30. /// If it does not receive an acknowledgment within this time, it will close the connection
  31. /// This value must be less than ``interval``.
  32. public var timeout: TimeAmount {
  33. didSet { self.checkInvariants() }
  34. }
  35. /// Send keepalive pings even if there are no calls in flight.
  36. public var permitWithoutCalls: Bool
  37. /// Maximum number of pings that can be sent when there is no data/header frame to be sent.
  38. public var maximumPingsWithoutData: UInt
  39. /// If there are no data/header frames being received:
  40. /// The minimum amount of time to wait between successive pings.
  41. public var minimumSentPingIntervalWithoutData: TimeAmount
  42. public init(
  43. interval: TimeAmount = .nanoseconds(Int64.max),
  44. timeout: TimeAmount = .seconds(20),
  45. permitWithoutCalls: Bool = false,
  46. maximumPingsWithoutData: UInt = 2,
  47. minimumSentPingIntervalWithoutData: TimeAmount = .minutes(5)
  48. ) {
  49. self.interval = interval
  50. self.timeout = timeout
  51. self.permitWithoutCalls = permitWithoutCalls
  52. self.maximumPingsWithoutData = maximumPingsWithoutData
  53. self.minimumSentPingIntervalWithoutData = minimumSentPingIntervalWithoutData
  54. self.checkInvariants()
  55. }
  56. }
  57. extension ClientConnectionKeepalive {
  58. /// Applies jitter to the ``interval``.
  59. ///
  60. /// The current ``interval`` will be adjusted by no more than `maxJitter` in either direction,
  61. /// that is the ``interval`` may increase or decrease by no more than `maxJitter`. As
  62. /// the ``timeout`` must be strictly less than the ``interval``, the lower range of the jittered
  63. /// interval is clamped to `max(interval - maxJitter, timeout + .nanoseconds(1)))`.
  64. ///
  65. /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
  66. /// be applied in either direction.
  67. public mutating func jitterInterval(byAtMost maxJitter: TimeAmount) {
  68. // The interval must be larger than the timeout so clamp the lower bound to be greater than
  69. // the timeout.
  70. let lowerBound = max(self.interval - maxJitter, self.timeout + .nanoseconds(1))
  71. let upperBound = self.interval + maxJitter
  72. self.interval = .nanoseconds(.random(in: lowerBound.nanoseconds ... upperBound.nanoseconds))
  73. }
  74. /// Returns a new ``ClientConnectionKeepalive`` with a jittered ``interval``.
  75. ///
  76. /// See also ``jitterInterval(byAtMost:)``.
  77. ///
  78. /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
  79. /// be applied in either direction.
  80. /// - Returns: A new ``ClientConnectionKeepalive``.
  81. public func jitteringInterval(byAtMost maxJitter: TimeAmount) -> Self {
  82. var copy = self
  83. copy.jitterInterval(byAtMost: maxJitter)
  84. return copy
  85. }
  86. }
  87. public struct ServerConnectionKeepalive: Hashable {
  88. private func checkInvariants(line: UInt = #line) {
  89. precondition(self.timeout < self.interval, "'timeout' must be less than 'interval'", line: line)
  90. }
  91. /// The amount of time to wait before sending a keepalive ping.
  92. public var interval: TimeAmount {
  93. didSet { self.checkInvariants() }
  94. }
  95. /// The amount of time to wait for an acknowledgment.
  96. /// If it does not receive an acknowledgment within this time, it will close the connection
  97. /// This value must be less than ``interval``.
  98. public var timeout: TimeAmount {
  99. didSet { self.checkInvariants() }
  100. }
  101. /// Send keepalive pings even if there are no calls in flight.
  102. public var permitWithoutCalls: Bool
  103. /// Maximum number of pings that can be sent when there is no data/header frame to be sent.
  104. public var maximumPingsWithoutData: UInt
  105. /// If there are no data/header frames being received:
  106. /// The minimum amount of time to wait between successive pings.
  107. public var minimumSentPingIntervalWithoutData: TimeAmount
  108. /// If there are no data/header frames being sent:
  109. /// The minimum amount of time expected between receiving successive pings.
  110. /// If the time between successive pings is less than this value, then the ping will be considered a bad ping from the peer.
  111. /// Such a ping counts as a "ping strike".
  112. public var minimumReceivedPingIntervalWithoutData: TimeAmount
  113. /// Maximum number of bad pings that the server will tolerate before sending an HTTP2 GOAWAY frame and closing the connection.
  114. /// Setting it to `0` allows the server to accept any number of bad pings.
  115. public var maximumPingStrikes: UInt
  116. public init(
  117. interval: TimeAmount = .hours(2),
  118. timeout: TimeAmount = .seconds(20),
  119. permitWithoutCalls: Bool = false,
  120. maximumPingsWithoutData: UInt = 2,
  121. minimumSentPingIntervalWithoutData: TimeAmount = .minutes(5),
  122. minimumReceivedPingIntervalWithoutData: TimeAmount = .minutes(5),
  123. maximumPingStrikes: UInt = 2
  124. ) {
  125. self.interval = interval
  126. self.timeout = timeout
  127. self.permitWithoutCalls = permitWithoutCalls
  128. self.maximumPingsWithoutData = maximumPingsWithoutData
  129. self.minimumSentPingIntervalWithoutData = minimumSentPingIntervalWithoutData
  130. self.minimumReceivedPingIntervalWithoutData = minimumReceivedPingIntervalWithoutData
  131. self.maximumPingStrikes = maximumPingStrikes
  132. self.checkInvariants()
  133. }
  134. }
  135. extension ServerConnectionKeepalive {
  136. /// Applies jitter to the ``interval``.
  137. ///
  138. /// The current ``interval`` will be adjusted by no more than `maxJitter` in either direction,
  139. /// that is the ``interval`` may increase or decrease by no more than `maxJitter`. As
  140. /// the ``timeout`` must be strictly less than the ``interval``, the lower range of the jittered
  141. /// interval is clamped to `max(interval - maxJitter, timeout + .nanoseconds(1)))`.
  142. ///
  143. /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
  144. /// be applied in either direction.
  145. public mutating func jitterInterval(byAtMost maxJitter: TimeAmount) {
  146. // The interval must be larger than the timeout so clamp the lower bound to be greater than
  147. // the timeout.
  148. let lowerBound = max(self.interval - maxJitter, self.timeout + .nanoseconds(1))
  149. let upperBound = self.interval + maxJitter
  150. self.interval = .nanoseconds(.random(in: lowerBound.nanoseconds ... upperBound.nanoseconds))
  151. }
  152. /// Returns a new ``ClientConnectionKeepalive`` with a jittered ``interval``.
  153. ///
  154. /// See also ``jitterInterval(byAtMost:)``.
  155. ///
  156. /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
  157. /// be applied in either direction.
  158. /// - Returns: A new ``ClientConnectionKeepalive``.
  159. public func jitteringInterval(byAtMost maxJitter: TimeAmount) -> Self {
  160. var copy = self
  161. copy.jitterInterval(byAtMost: maxJitter)
  162. return copy
  163. }
  164. }