ConnectionBackoff.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 Foundation
  17. /// Provides backoff timeouts for making a connection.
  18. ///
  19. /// This algorithm and defaults are determined by the gRPC connection backoff
  20. /// [documentation](https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md).
  21. public struct ConnectionBackoff: Sequence, Sendable {
  22. public typealias Iterator = ConnectionBackoffIterator
  23. /// The initial backoff in seconds.
  24. public var initialBackoff: TimeInterval
  25. /// The maximum backoff in seconds. Note that the backoff is _before_ jitter has been applied,
  26. /// this means that in practice the maximum backoff can be larger than this value.
  27. public var maximumBackoff: TimeInterval
  28. /// The backoff multiplier.
  29. public var multiplier: Double
  30. /// Backoff jitter; should be between 0 and 1.
  31. public var jitter: Double
  32. /// The minimum amount of time in seconds to try connecting.
  33. public var minimumConnectionTimeout: TimeInterval
  34. /// A limit on the number of times to attempt reconnection.
  35. public var retries: Retries
  36. public struct Retries: Hashable, Sendable {
  37. fileprivate enum Limit: Hashable, Sendable {
  38. case limited(Int)
  39. case unlimited
  40. }
  41. fileprivate var limit: Limit
  42. private init(_ limit: Limit) {
  43. self.limit = limit
  44. }
  45. /// An unlimited number of retry attempts.
  46. public static let unlimited = Retries(.unlimited)
  47. /// No retry attempts will be made.
  48. public static let none = Retries(.limited(0))
  49. /// A limited number of retry attempts. `limit` must be positive. Note that a limit of zero is
  50. /// identical to `.none`.
  51. public static func upTo(_ limit: Int) -> Retries {
  52. precondition(limit >= 0)
  53. return Retries(.limited(limit))
  54. }
  55. }
  56. /// Creates a ``ConnectionBackoff``.
  57. ///
  58. /// - Parameters:
  59. /// - initialBackoff: Initial backoff in seconds, defaults to 1.0.
  60. /// - maximumBackoff: Maximum backoff in seconds (prior to adding jitter), defaults to 120.0.
  61. /// - multiplier: Backoff multiplier, defaults to 1.6.
  62. /// - jitter: Backoff jitter, defaults to 0.2.
  63. /// - minimumConnectionTimeout: Minimum connection timeout in seconds, defaults to 20.0.
  64. /// - retries: A limit on the number of times to retry establishing a connection.
  65. /// Defaults to `.unlimited`.
  66. public init(
  67. initialBackoff: TimeInterval = 1.0,
  68. maximumBackoff: TimeInterval = 120.0,
  69. multiplier: Double = 1.6,
  70. jitter: Double = 0.2,
  71. minimumConnectionTimeout: TimeInterval = 20.0,
  72. retries: Retries = .unlimited
  73. ) {
  74. self.initialBackoff = initialBackoff
  75. self.maximumBackoff = maximumBackoff
  76. self.multiplier = multiplier
  77. self.jitter = jitter
  78. self.minimumConnectionTimeout = minimumConnectionTimeout
  79. self.retries = retries
  80. }
  81. public func makeIterator() -> ConnectionBackoff.Iterator {
  82. return Iterator(connectionBackoff: self)
  83. }
  84. }
  85. /// An iterator for ``ConnectionBackoff``.
  86. public class ConnectionBackoffIterator: IteratorProtocol {
  87. public typealias Element = (timeout: TimeInterval, backoff: TimeInterval)
  88. /// Creates a new connection backoff iterator with the given configuration.
  89. public init(connectionBackoff: ConnectionBackoff) {
  90. self.connectionBackoff = connectionBackoff
  91. self.unjitteredBackoff = connectionBackoff.initialBackoff
  92. // Since the first backoff is `initialBackoff` it must be generated here instead of
  93. // by `makeNextElement`.
  94. let backoff = min(connectionBackoff.initialBackoff, connectionBackoff.maximumBackoff)
  95. self.initialElement = self.makeElement(backoff: backoff)
  96. }
  97. /// The configuration being used.
  98. private var connectionBackoff: ConnectionBackoff
  99. /// The backoff in seconds, without jitter.
  100. private var unjitteredBackoff: TimeInterval
  101. /// The first element to return. Since the first backoff is defined as `initialBackoff` we can't
  102. /// compute it on-the-fly.
  103. private var initialElement: Element?
  104. /// Returns the next pair of connection timeout and backoff (in that order) to use should the
  105. /// connection attempt fail.
  106. public func next() -> Element? {
  107. // Should we make another element?
  108. switch self.connectionBackoff.retries.limit {
  109. // Always make a new element.
  110. case .unlimited:
  111. ()
  112. // Use up one from our remaining limit.
  113. case let .limited(limit) where limit > 0:
  114. self.connectionBackoff.retries.limit = .limited(limit - 1)
  115. // limit must be <= 0, no new element.
  116. case .limited:
  117. return nil
  118. }
  119. if let initial = self.initialElement {
  120. self.initialElement = nil
  121. return initial
  122. } else {
  123. return self.makeNextElement()
  124. }
  125. }
  126. /// Produces the next element to return.
  127. private func makeNextElement() -> Element {
  128. let unjittered = self.unjitteredBackoff * self.connectionBackoff.multiplier
  129. self.unjitteredBackoff = min(unjittered, self.connectionBackoff.maximumBackoff)
  130. let backoff = self.jittered(value: self.unjitteredBackoff)
  131. return self.makeElement(backoff: backoff)
  132. }
  133. /// Make a timeout-backoff pair from the given backoff. The timeout is the `max` of the backoff
  134. /// and `connectionBackoff.minimumConnectionTimeout`.
  135. private func makeElement(backoff: TimeInterval) -> Element {
  136. let timeout = max(backoff, self.connectionBackoff.minimumConnectionTimeout)
  137. return (timeout, backoff)
  138. }
  139. /// Adds 'jitter' to the given value.
  140. private func jittered(value: TimeInterval) -> TimeInterval {
  141. let lower = -self.connectionBackoff.jitter * value
  142. let upper = self.connectionBackoff.jitter * value
  143. return value + TimeInterval.random(in: lower ... upper)
  144. }
  145. }