/Sources/NIO/ChannelHandlers.swift

https://github.com/apple/swift-nio · Swift · 338 lines · 246 code · 48 blank · 44 comment · 48 complexity · a98ae391e29af4303cdeb325efcb70fd MD5 · raw file

  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the SwiftNIO open source project
  4. //
  5. // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
  6. // Licensed under Apache License v2.0
  7. //
  8. // See LICENSE.txt for license information
  9. // See CONTRIBUTORS.txt for the list of SwiftNIO project authors
  10. //
  11. // SPDX-License-Identifier: Apache-2.0
  12. //
  13. //===----------------------------------------------------------------------===//
  14. // Contains ChannelHandler implementations which are generic and can be re-used easily.
  15. //
  16. //
  17. /// A `ChannelHandler` that implements a backoff for a `ServerChannel` when accept produces an `IOError`.
  18. /// These errors are often recoverable by reducing the rate at which we call accept.
  19. public final class AcceptBackoffHandler: ChannelDuplexHandler, RemovableChannelHandler {
  20. public typealias InboundIn = Channel
  21. public typealias OutboundIn = Channel
  22. private var nextReadDeadlineNS: Optional<NIODeadline>
  23. private let backoffProvider: (IOError) -> TimeAmount?
  24. private var scheduledRead: Optional<Scheduled<Void>>
  25. /// Default implementation used as `backoffProvider` which delays accept by 1 second.
  26. public static func defaultBackoffProvider(error: IOError) -> TimeAmount? {
  27. return .seconds(1)
  28. }
  29. /// Create a new instance
  30. ///
  31. /// - parameters:
  32. /// - backoffProvider: returns a `TimeAmount` which will be the amount of time to wait before attempting another `read`.
  33. public init(backoffProvider: @escaping (IOError) -> TimeAmount? = AcceptBackoffHandler.defaultBackoffProvider) {
  34. self.backoffProvider = backoffProvider
  35. self.nextReadDeadlineNS = nil
  36. self.scheduledRead = nil
  37. }
  38. public func read(context: ChannelHandlerContext) {
  39. // If we already have a read scheduled there is no need to schedule another one.
  40. guard scheduledRead == nil else { return }
  41. if let deadline = self.nextReadDeadlineNS {
  42. let now = NIODeadline.now()
  43. if now >= deadline {
  44. // The backoff already expired, just do a read.
  45. doRead(context)
  46. } else {
  47. // Schedule the read to be executed after the backoff time elapsed.
  48. scheduleRead(at: deadline, context: context)
  49. }
  50. } else {
  51. context.read()
  52. }
  53. }
  54. public func errorCaught(context: ChannelHandlerContext, error: Error) {
  55. if let ioError = error as? IOError {
  56. if let amount = backoffProvider(ioError) {
  57. self.nextReadDeadlineNS = .now() + amount
  58. if let scheduled = self.scheduledRead {
  59. scheduled.cancel()
  60. scheduleRead(at: self.nextReadDeadlineNS!, context: context)
  61. }
  62. }
  63. }
  64. context.fireErrorCaught(error)
  65. }
  66. public func channelInactive(context: ChannelHandlerContext) {
  67. if let scheduled = self.scheduledRead {
  68. scheduled.cancel()
  69. self.scheduledRead = nil
  70. }
  71. self.nextReadDeadlineNS = nil
  72. context.fireChannelInactive()
  73. }
  74. public func handlerRemoved(context: ChannelHandlerContext) {
  75. if let scheduled = self.scheduledRead {
  76. // Cancel the previous scheduled read and trigger a read directly. This is needed as otherwise we may never read again.
  77. scheduled.cancel()
  78. self.scheduledRead = nil
  79. context.read()
  80. }
  81. self.nextReadDeadlineNS = nil
  82. }
  83. private func scheduleRead(at: NIODeadline, context: ChannelHandlerContext) {
  84. self.scheduledRead = context.eventLoop.scheduleTask(deadline: at) {
  85. self.doRead(context)
  86. }
  87. }
  88. private func doRead(_ context: ChannelHandlerContext) {
  89. /// Reset the backoff time and read.
  90. self.nextReadDeadlineNS = nil
  91. self.scheduledRead = nil
  92. context.read()
  93. }
  94. }
  95. /**
  96. ChannelHandler implementation which enforces back-pressure by stopping to read from the remote peer when it cannot write back fast enough.
  97. It will start reading again once pending data was written.
  98. */
  99. public final class BackPressureHandler: ChannelDuplexHandler, RemovableChannelHandler {
  100. public typealias OutboundIn = NIOAny
  101. public typealias InboundIn = ByteBuffer
  102. public typealias InboundOut = ByteBuffer
  103. public typealias OutboundOut = ByteBuffer
  104. private var pendingRead = false
  105. private var writable: Bool = true
  106. public init() { }
  107. public func read(context: ChannelHandlerContext) {
  108. if writable {
  109. context.read()
  110. } else {
  111. pendingRead = true
  112. }
  113. }
  114. public func channelWritabilityChanged(context: ChannelHandlerContext) {
  115. self.writable = context.channel.isWritable
  116. if writable {
  117. mayRead(context: context)
  118. } else {
  119. context.flush()
  120. }
  121. // Propagate the event as the user may still want to do something based on it.
  122. context.fireChannelWritabilityChanged()
  123. }
  124. public func handlerRemoved(context: ChannelHandlerContext) {
  125. mayRead(context: context)
  126. }
  127. private func mayRead(context: ChannelHandlerContext) {
  128. if pendingRead {
  129. pendingRead = false
  130. context.read()
  131. }
  132. }
  133. }
  134. /// Triggers an IdleStateEvent when a Channel has not performed read, write, or both operation for a while.
  135. public final class IdleStateHandler: ChannelDuplexHandler, RemovableChannelHandler {
  136. public typealias InboundIn = NIOAny
  137. public typealias InboundOut = NIOAny
  138. public typealias OutboundIn = NIOAny
  139. public typealias OutboundOut = NIOAny
  140. ///A user event triggered by IdleStateHandler when a Channel is idle.
  141. public enum IdleStateEvent {
  142. /// Will be triggered when no write was performed for the specified amount of time
  143. case write
  144. /// Will be triggered when no read was performed for the specified amount of time
  145. case read
  146. /// Will be triggered when neither read nor write was performed for the specified amount of time
  147. case all
  148. }
  149. public let readTimeout: TimeAmount?
  150. public let writeTimeout: TimeAmount?
  151. public let allTimeout: TimeAmount?
  152. private var reading = false
  153. private var lastReadTime: NIODeadline = .distantPast
  154. private var lastWriteCompleteTime: NIODeadline = .distantPast
  155. private var scheduledReaderTask: Optional<Scheduled<Void>>
  156. private var scheduledWriterTask: Optional<Scheduled<Void>>
  157. private var scheduledAllTask: Optional<Scheduled<Void>>
  158. public init(readTimeout: TimeAmount? = nil, writeTimeout: TimeAmount? = nil, allTimeout: TimeAmount? = nil) {
  159. self.readTimeout = readTimeout
  160. self.writeTimeout = writeTimeout
  161. self.allTimeout = allTimeout
  162. self.scheduledAllTask = nil
  163. self.scheduledReaderTask = nil
  164. self.scheduledWriterTask = nil
  165. }
  166. public func handlerAdded(context: ChannelHandlerContext) {
  167. if context.channel.isActive {
  168. initIdleTasks(context)
  169. }
  170. }
  171. public func handlerRemoved(context: ChannelHandlerContext) {
  172. cancelIdleTasks(context)
  173. }
  174. public func channelActive(context: ChannelHandlerContext) {
  175. initIdleTasks(context)
  176. context.fireChannelActive()
  177. }
  178. public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
  179. if readTimeout != nil || allTimeout != nil {
  180. reading = true
  181. }
  182. context.fireChannelRead(data)
  183. }
  184. public func channelReadComplete(context: ChannelHandlerContext) {
  185. if (readTimeout != nil || allTimeout != nil) && reading {
  186. lastReadTime = .now()
  187. reading = false
  188. }
  189. context.fireChannelReadComplete()
  190. }
  191. public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
  192. if writeTimeout == nil && allTimeout == nil {
  193. context.write(data, promise: promise)
  194. return
  195. }
  196. let writePromise = promise ?? context.eventLoop.makePromise()
  197. writePromise.futureResult.whenComplete { (_: Result<Void, Error>) in
  198. self.lastWriteCompleteTime = .now()
  199. }
  200. context.write(data, promise: writePromise)
  201. }
  202. private func shouldReschedule(_ context: ChannelHandlerContext) -> Bool {
  203. if context.channel.isActive {
  204. return true
  205. }
  206. return false
  207. }
  208. private func makeReadTimeoutTask(_ context: ChannelHandlerContext, _ timeout: TimeAmount) -> (() -> Void) {
  209. return {
  210. guard self.shouldReschedule(context) else {
  211. return
  212. }
  213. if self.reading {
  214. self.scheduledReaderTask = context.eventLoop.scheduleTask(in: timeout, self.makeReadTimeoutTask(context, timeout))
  215. return
  216. }
  217. let diff = .now() - self.lastReadTime
  218. if diff >= timeout {
  219. // Reader is idle - set a new timeout and trigger an event through the pipeline
  220. self.scheduledReaderTask = context.eventLoop.scheduleTask(in: timeout, self.makeReadTimeoutTask(context, timeout))
  221. context.fireUserInboundEventTriggered(IdleStateEvent.read)
  222. } else {
  223. // Read occurred before the timeout - set a new timeout with shorter delay.
  224. self.scheduledReaderTask = context.eventLoop.scheduleTask(deadline: self.lastReadTime + timeout, self.makeReadTimeoutTask(context, timeout))
  225. }
  226. }
  227. }
  228. private func makeWriteTimeoutTask(_ context: ChannelHandlerContext, _ timeout: TimeAmount) -> (() -> Void) {
  229. return {
  230. guard self.shouldReschedule(context) else {
  231. return
  232. }
  233. let lastWriteTime = self.lastWriteCompleteTime
  234. let diff = .now() - lastWriteTime
  235. if diff >= timeout {
  236. // Writer is idle - set a new timeout and notify the callback.
  237. self.scheduledWriterTask = context.eventLoop.scheduleTask(in: timeout, self.makeWriteTimeoutTask(context, timeout))
  238. context.fireUserInboundEventTriggered(IdleStateEvent.write)
  239. } else {
  240. // Write occurred before the timeout - set a new timeout with shorter delay.
  241. self.scheduledWriterTask = context.eventLoop.scheduleTask(deadline: self.lastWriteCompleteTime + timeout, self.makeWriteTimeoutTask(context, timeout))
  242. }
  243. }
  244. }
  245. private func makeAllTimeoutTask(_ context: ChannelHandlerContext, _ timeout: TimeAmount) -> (() -> Void) {
  246. return {
  247. guard self.shouldReschedule(context) else {
  248. return
  249. }
  250. if self.reading {
  251. self.scheduledReaderTask = context.eventLoop.scheduleTask(in: timeout, self.makeAllTimeoutTask(context, timeout))
  252. return
  253. }
  254. let lastRead = self.lastReadTime
  255. let lastWrite = self.lastWriteCompleteTime
  256. let latestLast = max(lastRead, lastWrite)
  257. let diff = .now() - latestLast
  258. if diff >= timeout {
  259. // Reader is idle - set a new timeout and trigger an event through the pipeline
  260. self.scheduledReaderTask = context.eventLoop.scheduleTask(in: timeout, self.makeAllTimeoutTask(context, timeout))
  261. context.fireUserInboundEventTriggered(IdleStateEvent.all)
  262. } else {
  263. // Read occurred before the timeout - set a new timeout with shorter delay.
  264. self.scheduledReaderTask = context.eventLoop.scheduleTask(deadline: latestLast + timeout, self.makeAllTimeoutTask(context, timeout))
  265. }
  266. }
  267. }
  268. private func schedule(_ context: ChannelHandlerContext, _ amount: TimeAmount?, _ body: @escaping (ChannelHandlerContext, TimeAmount) -> (() -> Void) ) -> Scheduled<Void>? {
  269. if let timeout = amount {
  270. return context.eventLoop.scheduleTask(in: timeout, body(context, timeout))
  271. }
  272. return nil
  273. }
  274. private func initIdleTasks(_ context: ChannelHandlerContext) {
  275. let now = NIODeadline.now()
  276. lastReadTime = now
  277. lastWriteCompleteTime = now
  278. scheduledReaderTask = schedule(context, readTimeout, makeReadTimeoutTask)
  279. scheduledWriterTask = schedule(context, writeTimeout, makeWriteTimeoutTask)
  280. scheduledAllTask = schedule(context, allTimeout, makeAllTimeoutTask)
  281. }
  282. private func cancelIdleTasks(_ context: ChannelHandlerContext) {
  283. scheduledReaderTask?.cancel()
  284. scheduledWriterTask?.cancel()
  285. scheduledAllTask?.cancel()
  286. scheduledReaderTask = nil
  287. scheduledWriterTask = nil
  288. scheduledAllTask = nil
  289. }
  290. }