PlatformSupport.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 NIO
  17. import NIOTransportServices
  18. import Logging
  19. /// How a network implementation should be chosen.
  20. public struct NetworkPreference: Hashable {
  21. private enum Wrapped: Hashable {
  22. case best
  23. case userDefined(NetworkImplementation)
  24. }
  25. private var wrapped: Wrapped
  26. private init(_ wrapped: Wrapped) {
  27. self.wrapped = wrapped
  28. }
  29. /// Use the best available, that is, Network.framework (and NIOTransportServices) when it is
  30. /// available on Darwin platforms (macOS 10.14+, iOS 12.0+, tvOS 12.0+, watchOS 6.0+), and
  31. /// falling back to the POSIX network model otherwise.
  32. public static let best = NetworkPreference(.best)
  33. /// Use the given implementation. Doing so may require additional availability checks depending
  34. /// on the implementation.
  35. public static func userDefined(_ implementation: NetworkImplementation) -> NetworkPreference {
  36. return NetworkPreference(.userDefined(implementation))
  37. }
  38. }
  39. /// The network implementation to use: POSIX sockets or Network.framework. This also determines
  40. /// which variant of NIO to use; NIO or NIOTransportServices, respectively.
  41. public struct NetworkImplementation: Hashable {
  42. fileprivate enum Wrapped: Hashable {
  43. case networkFramework
  44. case posix
  45. }
  46. fileprivate var wrapped: Wrapped
  47. private init(_ wrapped: Wrapped) {
  48. self.wrapped = wrapped
  49. }
  50. #if canImport(Network)
  51. /// Network.framework (NIOTransportServices).
  52. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
  53. public static let networkFramework = NetworkImplementation(.networkFramework)
  54. #endif
  55. /// POSIX (NIO).
  56. public static let posix = NetworkImplementation(.posix)
  57. }
  58. extension NetworkPreference {
  59. /// The network implementation, and by extension the NIO variant which will be used.
  60. ///
  61. /// Network.framework is available on macOS 10.14+, iOS 12.0+, tvOS 12.0+ and watchOS 6.0+.
  62. ///
  63. /// This isn't directly useful when implementing code which branches on the network preference
  64. /// since that code will still need the appropriate availability check:
  65. ///
  66. /// - `@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)`, or
  67. /// - `#available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)`.
  68. public var implementation: NetworkImplementation {
  69. switch self.wrapped {
  70. case .best:
  71. #if canImport(Network)
  72. if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
  73. return .networkFramework
  74. } else {
  75. // Older platforms must use the POSIX loop.
  76. return .posix
  77. }
  78. #else
  79. return .posix
  80. #endif
  81. case .userDefined(let implementation):
  82. return implementation
  83. }
  84. }
  85. }
  86. // MARK: - Generic Bootstraps
  87. // TODO: Revisit the handling of NIO/NIOTS once https://github.com/apple/swift-nio/issues/796
  88. // is addressed.
  89. /// This protocol is intended as a layer of abstraction over `ClientBootstrap` and
  90. /// `NIOTSConnectionBootstrap`.
  91. public protocol ClientBootstrapProtocol {
  92. func connect(to: SocketAddress) -> EventLoopFuture<Channel>
  93. func connect(host: String, port: Int) -> EventLoopFuture<Channel>
  94. func connect(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
  95. func connectTimeout(_ timeout: TimeAmount) -> Self
  96. func channelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  97. func channelInitializer(_ handler: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  98. }
  99. extension ClientBootstrap: ClientBootstrapProtocol {}
  100. #if canImport(Network)
  101. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
  102. extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {}
  103. #endif
  104. /// This protocol is intended as a layer of abstraction over `ServerBootstrap` and
  105. /// `NIOTSListenerBootstrap`.
  106. public protocol ServerBootstrapProtocol {
  107. func bind(to: SocketAddress) -> EventLoopFuture<Channel>
  108. func bind(host: String, port: Int) -> EventLoopFuture<Channel>
  109. func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
  110. func serverChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  111. func serverChannelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  112. func childChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  113. func childChannelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  114. }
  115. extension ServerBootstrap: ServerBootstrapProtocol {}
  116. #if canImport(Network)
  117. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
  118. extension NIOTSListenerBootstrap: ServerBootstrapProtocol {}
  119. #endif
  120. // MARK: - Bootstrap / EventLoopGroup helpers
  121. public enum PlatformSupport {
  122. static let logger = Logger(subsystem: .nio)
  123. /// Makes a new event loop group based on the network preference.
  124. ///
  125. /// If `.best` is chosen and `Network.framework` is available then `NIOTSEventLoopGroup` will
  126. /// be returned. A `MultiThreadedEventLoopGroup` will be returned otherwise.
  127. ///
  128. /// - Parameter loopCount: The number of event loops to create in the event loop group.
  129. /// - Parameter networkPreference: Network preference; defaulting to `.best`.
  130. public static func makeEventLoopGroup(
  131. loopCount: Int,
  132. networkPreference: NetworkPreference = .best,
  133. logger: Logger? = nil
  134. ) -> EventLoopGroup {
  135. let logger = logger ?? PlatformSupport.logger
  136. logger.debug("making EventLoopGroup for \(networkPreference) network preference")
  137. switch networkPreference.implementation.wrapped {
  138. case .networkFramework:
  139. #if canImport(Network)
  140. guard #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) else {
  141. logger.critical("Network.framework can be imported but is not supported on this platform")
  142. // This is gated by the availability of `.networkFramework` so should never happen.
  143. fatalError(".networkFramework is being used on an unsupported platform")
  144. }
  145. logger.debug("created NIOTSEventLoopGroup for \(networkPreference) preference")
  146. return NIOTSEventLoopGroup(loopCount: loopCount)
  147. #else
  148. fatalError(".networkFramework is being used on an unsupported platform")
  149. #endif
  150. case .posix:
  151. logger.debug("created MultiThreadedEventLoopGroup for \(networkPreference) preference")
  152. return MultiThreadedEventLoopGroup(numberOfThreads: loopCount)
  153. }
  154. }
  155. /// Makes a new client bootstrap using the given `EventLoopGroup`.
  156. ///
  157. /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a
  158. /// `NIOTSConnectionBootstrap`, otherwise it will be a `ClientBootstrap`.
  159. ///
  160. /// - Parameter group: The `EventLoopGroup` to use.
  161. public static func makeClientBootstrap(group: EventLoopGroup, logger: Logger? = nil) -> ClientBootstrapProtocol {
  162. let logger = logger ?? PlatformSupport.logger
  163. logger.debug("making client bootstrap with event loop group of type \(type(of: group))")
  164. #if canImport(Network)
  165. if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
  166. if let tsGroup = group as? NIOTSEventLoopGroup {
  167. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
  168. return NIOTSConnectionBootstrap(group: tsGroup)
  169. } else if let qosEventLoop = group as? QoSEventLoop {
  170. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
  171. return NIOTSConnectionBootstrap(group: qosEventLoop)
  172. }
  173. logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ClientBootstrap")
  174. }
  175. #endif
  176. logger.debug("creating a ClientBootstrap")
  177. return ClientBootstrap(group: group)
  178. }
  179. /// Makes a new server bootstrap using the given `EventLoopGroup`.
  180. ///
  181. /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a
  182. /// `NIOTSListenerBootstrap`, otherwise it will be a `ServerBootstrap`.
  183. ///
  184. /// - Parameter group: The `EventLoopGroup` to use.
  185. public static func makeServerBootstrap(group: EventLoopGroup, logger: Logger? = nil) -> ServerBootstrapProtocol {
  186. let logger = logger ?? PlatformSupport.logger
  187. logger.debug("making server bootstrap with event loop group of type \(type(of: group))")
  188. #if canImport(Network)
  189. if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
  190. if let tsGroup = group as? NIOTSEventLoopGroup {
  191. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
  192. return NIOTSListenerBootstrap(group: tsGroup)
  193. } else if let qosEventLoop = group as? QoSEventLoop {
  194. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
  195. return NIOTSListenerBootstrap(group: qosEventLoop)
  196. }
  197. logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ServerBootstrap")
  198. }
  199. #endif
  200. logger.debug("creating a ServerBootstrap")
  201. return ServerBootstrap(group: group)
  202. }
  203. }