HTTP2ConnectionState.swift 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * Copyright 2021, 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 Logging
  17. import NIO
  18. import NIOHTTP2
  19. /// This struct models the state of an HTTP/2 connection and provides the means to indirectly track
  20. /// active and available HTTP/2 streams on that connection.
  21. ///
  22. /// The state -- once ready -- holds a multiplexer which it yields when an available 'token' is
  23. /// borrowed. One token corresponds to the creation of one HTTP/2 stream. The caller is responsible
  24. /// for later returning the token.
  25. internal struct HTTP2ConnectionState {
  26. /// An identifier for this pooled connection.
  27. internal let id: ObjectIdentifier
  28. /// Indicates whether the pooled connection is idle.
  29. internal var isIdle: Bool {
  30. return self.state.isIdle
  31. }
  32. /// The number of tokens currently available for this connection. `availableTokens` must be
  33. /// greater than zero for `borrowTokens` to be called.
  34. ///
  35. /// Note that it is also possible for `availableTokens` to be negative.
  36. internal var availableTokens: Int {
  37. switch self.state {
  38. case let .ready(ready):
  39. return ready.availableTokens
  40. case .idle, .connectingOrBackingOff:
  41. return 0
  42. }
  43. }
  44. /// The number of tokens currently borrowed from this connection.
  45. internal var borrowedTokens: Int {
  46. switch self.state {
  47. case let .ready(ready):
  48. return ready.borrowedTokens
  49. case .idle, .connectingOrBackingOff:
  50. return 0
  51. }
  52. }
  53. /// The state of the pooled connection.
  54. private var state: State
  55. private enum State {
  56. /// No connection has been asked for, there are no tokens available.
  57. case idle
  58. /// A connection attempt is underway or we may be waiting to attempt to connect again.
  59. case connectingOrBackingOff
  60. /// We have an active connection which may have tokens borrowed.
  61. case ready(ReadyState)
  62. /// Whether the state is `idle`.
  63. var isIdle: Bool {
  64. switch self {
  65. case .idle:
  66. return true
  67. case .connectingOrBackingOff, .ready:
  68. return false
  69. }
  70. }
  71. }
  72. private struct ReadyState {
  73. internal var multiplexer: HTTP2StreamMultiplexer
  74. internal var borrowedTokens: Int
  75. internal var tokenLimit: Int
  76. internal init(multiplexer: HTTP2StreamMultiplexer) {
  77. self.multiplexer = multiplexer
  78. self.borrowedTokens = 0
  79. // 100 is a common value for HTTP/2 SETTINGS_MAX_CONCURRENT_STREAMS so we assume this value
  80. // until we know better.
  81. self.tokenLimit = 100
  82. }
  83. internal var availableTokens: Int {
  84. return self.tokenLimit - self.borrowedTokens
  85. }
  86. internal mutating func borrowTokens(_ count: Int) -> (HTTP2StreamMultiplexer, Int) {
  87. self.borrowedTokens += count
  88. assert(self.borrowedTokens <= self.tokenLimit)
  89. return (self.multiplexer, self.borrowedTokens)
  90. }
  91. internal mutating func returnToken() {
  92. self.borrowedTokens -= 1
  93. assert(self.borrowedTokens >= 0)
  94. }
  95. internal mutating func updateTokenLimit(_ limit: Int) -> Int {
  96. let oldLimit = self.tokenLimit
  97. self.tokenLimit = limit
  98. return oldLimit
  99. }
  100. }
  101. internal init(connectionManagerID: ObjectIdentifier) {
  102. self.id = connectionManagerID
  103. self.state = .idle
  104. }
  105. // MARK: - Lease Management
  106. /// Borrow tokens from the pooled connection.
  107. ///
  108. /// Each borrowed token corresponds to the creation of one HTTP/2 stream using the multiplexer
  109. /// returned from this call. The caller must return each token once the stream is no longer
  110. /// required using `returnToken(multiplexerID:)` where `multiplexerID` is the `ObjectIdentifier`
  111. /// for the `HTTP2StreamMultiplexer` returned from this call.
  112. ///
  113. /// - Parameter tokensToBorrow: The number of tokens to borrow. This *must not*
  114. /// exceed `availableTokens`.
  115. /// - Returns: A tuple of the `HTTP2StreamMultiplexer` on which streams should be created and
  116. /// total number of tokens which have been borrowed from this connection.
  117. mutating func borrowTokens(_ tokensToBorrow: Int) -> (HTTP2StreamMultiplexer, Int) {
  118. switch self.state {
  119. case var .ready(ready):
  120. let result = ready.borrowTokens(tokensToBorrow)
  121. self.state = .ready(ready)
  122. return result
  123. case .idle, .connectingOrBackingOff:
  124. // `availableTokens` is zero for these two states and a precondition for calling this function
  125. // is that `tokensToBorrow` must not exceed the available tokens.
  126. preconditionFailure()
  127. }
  128. }
  129. /// Return a single token to the pooled connection.
  130. mutating func returnToken() {
  131. switch self.state {
  132. case var .ready(ready):
  133. ready.returnToken()
  134. self.state = .ready(ready)
  135. case .idle, .connectingOrBackingOff:
  136. // A token may have been returned after the connection dropped.
  137. ()
  138. }
  139. }
  140. /// Updates the maximum number of tokens a connection may vend at any given time and returns the
  141. /// previous limit.
  142. ///
  143. /// If the new limit is higher than the old limit then there may now be some tokens available
  144. /// (i.e. `availableTokens > 0`). If the new limit is lower than the old limit `availableTokens`
  145. /// will decrease and this connection may not have any available tokens.
  146. ///
  147. /// - Parameters:
  148. /// - newValue: The maximum number of tokens a connection may vend at a given time.
  149. /// - Returns: The previous token limit.
  150. mutating func updateMaximumTokens(_ newValue: Int) -> Int {
  151. switch self.state {
  152. case var .ready(ready):
  153. let oldLimit = ready.updateTokenLimit(newValue)
  154. self.state = .ready(ready)
  155. return oldLimit
  156. case .idle, .connectingOrBackingOff:
  157. preconditionFailure()
  158. }
  159. }
  160. /// Notify the state that a connection attempt is about to start.
  161. mutating func willStartConnecting() {
  162. switch self.state {
  163. case .idle, .ready:
  164. // We can start connecting from the 'ready' state again if the connection was dropped.
  165. self.state = .connectingOrBackingOff
  166. case .connectingOrBackingOff:
  167. preconditionFailure()
  168. }
  169. }
  170. /// The connection attempt succeeded.
  171. ///
  172. /// - Parameter multiplexer: The `HTTP2StreamMultiplexer` from the connection.
  173. mutating func connected(multiplexer: HTTP2StreamMultiplexer) {
  174. switch self.state {
  175. case .connectingOrBackingOff:
  176. self.state = .ready(ReadyState(multiplexer: multiplexer))
  177. case .idle, .ready:
  178. preconditionFailure()
  179. }
  180. }
  181. /// Notify the state of a change in connectivity from the guts of the connection (as emitted by
  182. /// the `ConnectivityStateDelegate`).
  183. ///
  184. /// - Parameter state: The new state.
  185. /// - Returns: Any action to perform as a result of the state change.
  186. mutating func connectivityStateChanged(to state: ConnectivityState) -> StateChangeAction {
  187. // We only care about a few transitions as we mostly rely on our own state transitions. Namely,
  188. // we care about a change from ready to transient failure (as we need to invalidate any borrowed
  189. // tokens and start a new connection). We also care about shutting down.
  190. switch (state, self.state) {
  191. case (.idle, _):
  192. // We always need to invalidate any state when the channel becomes idle again.
  193. self.state = .idle
  194. return .nothing
  195. case (.connecting, _),
  196. (.ready, _):
  197. // We may bounce between 'connecting' and 'transientFailure' when we're in
  198. // the 'connectingOrBackingOff', it's okay to ignore 'connecting' here.
  199. //
  200. // We never pay attention to receiving 'ready', rather we rely on 'connected(multiplexer:)'
  201. // instead.
  202. return .nothing
  203. case (.transientFailure, .ready):
  204. // If we're ready and hit a transient failure, we must start connecting again. We'll defer our
  205. // own state transition until 'willStartConnecting()' is called.
  206. return .startConnectingAgain
  207. case (.transientFailure, .idle),
  208. (.transientFailure, .connectingOrBackingOff):
  209. return .nothing
  210. case (.shutdown, _):
  211. // The connection has been shutdown. We shouldn't pay attention to it anymore.
  212. return .removeFromConnectionList
  213. }
  214. }
  215. internal enum StateChangeAction: Hashable {
  216. /// Do nothing.
  217. case nothing
  218. /// Remove the connection from the pooled connections, it has been shutdown.
  219. case removeFromConnectionList
  220. /// Check if any waiters exist for the connection.
  221. case checkWaiters
  222. /// The connection dropped: ask for a new one.
  223. case startConnectingAgain
  224. }
  225. }