PlatformSupport.swift 8.8 KB

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