PlatformSupport.swift 8.8 KB

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