PlatformSupport.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. guard #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) else {
  73. // This is gated by the availability of `.networkFramework` so should never happen.
  74. fatalError(".networkFramework is being used on an unsupported platform")
  75. }
  76. return .networkFramework
  77. #else
  78. return .posix
  79. #endif
  80. case .userDefined(let implementation):
  81. return implementation
  82. }
  83. }
  84. }
  85. // MARK: - Generic Bootstraps
  86. // TODO: Revisit the handling of NIO/NIOTS once https://github.com/apple/swift-nio/issues/796
  87. // is addressed.
  88. /// This protocol is intended as a layer of abstraction over `ClientBootstrap` and
  89. /// `NIOTSConnectionBootstrap`.
  90. public protocol ClientBootstrapProtocol {
  91. func connect(to: SocketAddress) -> EventLoopFuture<Channel>
  92. func connect(host: String, port: Int) -> EventLoopFuture<Channel>
  93. func connect(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
  94. func connectTimeout(_ timeout: TimeAmount) -> Self
  95. func channelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  96. func channelInitializer(_ handler: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  97. }
  98. extension ClientBootstrap: ClientBootstrapProtocol {}
  99. #if canImport(Network)
  100. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
  101. extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {}
  102. #endif
  103. /// This protocol is intended as a layer of abstraction over `ServerBootstrap` and
  104. /// `NIOTSListenerBootstrap`.
  105. public protocol ServerBootstrapProtocol {
  106. func bind(to: SocketAddress) -> EventLoopFuture<Channel>
  107. func bind(host: String, port: Int) -> EventLoopFuture<Channel>
  108. func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
  109. func serverChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  110. func serverChannelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  111. func childChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self
  112. func childChannelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
  113. }
  114. extension ServerBootstrap: ServerBootstrapProtocol {}
  115. #if canImport(Network)
  116. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
  117. extension NIOTSListenerBootstrap: ServerBootstrapProtocol {}
  118. #endif
  119. // MARK: - Bootstrap / EventLoopGroup helpers
  120. public enum PlatformSupport {
  121. static let logger = Logger(subsystem: .nio)
  122. /// Makes a new event loop group based on the network preference.
  123. ///
  124. /// If `.best` is chosen and `Network.framework` is available then `NIOTSEventLoopGroup` will
  125. /// be returned. A `MultiThreadedEventLoopGroup` will be returned otherwise.
  126. ///
  127. /// - Parameter loopCount: The number of event loops to create in the event loop group.
  128. /// - Parameter networkPreference: Network preference; defaulting to `.best`.
  129. public static func makeEventLoopGroup(
  130. loopCount: Int,
  131. networkPreference: NetworkPreference = .best,
  132. logger: Logger? = nil
  133. ) -> EventLoopGroup {
  134. let logger = logger ?? PlatformSupport.logger
  135. logger.debug("making EventLoopGroup for \(networkPreference) network preference")
  136. switch networkPreference.implementation.wrapped {
  137. case .networkFramework:
  138. #if canImport(Network)
  139. guard #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) else {
  140. logger.critical("Network.framework can be imported but is not supported on this platform")
  141. // This is gated by the availability of `.networkFramework` so should never happen.
  142. fatalError(".networkFramework is being used on an unsupported platform")
  143. }
  144. logger.debug("created NIOTSEventLoopGroup for \(networkPreference) preference")
  145. return NIOTSEventLoopGroup(loopCount: loopCount)
  146. #else
  147. fatalError(".networkFramework is being used on an unsupported platform")
  148. #endif
  149. case .posix:
  150. logger.debug("created MultiThreadedEventLoopGroup for \(networkPreference) preference")
  151. return MultiThreadedEventLoopGroup(numberOfThreads: loopCount)
  152. }
  153. }
  154. /// Makes a new client bootstrap using the given `EventLoopGroup`.
  155. ///
  156. /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a
  157. /// `NIOTSConnectionBootstrap`, otherwise it will be a `ClientBootstrap`.
  158. ///
  159. /// - Parameter group: The `EventLoopGroup` to use.
  160. public static func makeClientBootstrap(group: EventLoopGroup, logger: Logger? = nil) -> ClientBootstrapProtocol {
  161. let logger = logger ?? PlatformSupport.logger
  162. logger.debug("making client bootstrap with event loop group of type \(type(of: group))")
  163. #if canImport(Network)
  164. if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
  165. if let tsGroup = group as? NIOTSEventLoopGroup {
  166. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
  167. return NIOTSConnectionBootstrap(group: tsGroup)
  168. } else if let qosEventLoop = group as? QoSEventLoop {
  169. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
  170. return NIOTSConnectionBootstrap(group: qosEventLoop)
  171. }
  172. logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ClientBootstrap")
  173. }
  174. #endif
  175. logger.debug("creating a ClientBootstrap")
  176. return ClientBootstrap(group: group)
  177. }
  178. /// Makes a new server bootstrap using the given `EventLoopGroup`.
  179. ///
  180. /// If the `EventLoopGroup` is a `NIOTSEventLoopGroup` then the returned bootstrap will be a
  181. /// `NIOTSListenerBootstrap`, otherwise it will be a `ServerBootstrap`.
  182. ///
  183. /// - Parameter group: The `EventLoopGroup` to use.
  184. public static func makeServerBootstrap(group: EventLoopGroup, logger: Logger? = nil) -> ServerBootstrapProtocol {
  185. let logger = logger ?? PlatformSupport.logger
  186. logger.debug("making server bootstrap with event loop group of type \(type(of: group))")
  187. #if canImport(Network)
  188. if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
  189. if let tsGroup = group as? NIOTSEventLoopGroup {
  190. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
  191. return NIOTSListenerBootstrap(group: tsGroup)
  192. } else if let qosEventLoop = group as? QoSEventLoop {
  193. logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
  194. return NIOTSListenerBootstrap(group: qosEventLoop)
  195. }
  196. logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ServerBootstrap")
  197. }
  198. #endif
  199. logger.debug("creating a ServerBootstrap")
  200. return ServerBootstrap(group: group)
  201. }
  202. }