ConnectionBackoff.swift 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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 {
  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. /// Creates a `ConnectionBackoff`.
  35. ///
  36. /// - Parameters:
  37. /// - initialBackoff: Initial backoff in seconds, defaults to 1.0.
  38. /// - maximumBackoff: Maximum backoff in seconds (prior to adding jitter), defaults to 120.0.
  39. /// - multiplier: Backoff multiplier, defaults to 1.6.
  40. /// - jitter: Backoff jitter, defaults to 0.2.
  41. /// - minimumConnectionTimeout: Minimum connection timeout in seconds, defaults to 20.0.
  42. public init(
  43. initialBackoff: TimeInterval = 1.0,
  44. maximumBackoff: TimeInterval = 120.0,
  45. multiplier: Double = 1.6,
  46. jitter: Double = 0.2,
  47. minimumConnectionTimeout: TimeInterval = 20.0
  48. ) {
  49. self.initialBackoff = initialBackoff
  50. self.maximumBackoff = maximumBackoff
  51. self.multiplier = multiplier
  52. self.jitter = jitter
  53. self.minimumConnectionTimeout = minimumConnectionTimeout
  54. }
  55. public func makeIterator() -> ConnectionBackoff.Iterator {
  56. return Iterator(connectionBackoff: self)
  57. }
  58. }
  59. /// An iterator for `ConnectionBackoff`.
  60. public class ConnectionBackoffIterator: IteratorProtocol {
  61. public typealias Element = (timeout: TimeInterval, backoff: TimeInterval)
  62. /// Creates a new connection backoff iterator with the given configuration.
  63. public init(connectionBackoff: ConnectionBackoff) {
  64. self.connectionBackoff = connectionBackoff
  65. self.unjitteredBackoff = connectionBackoff.initialBackoff
  66. // Since the first backoff is `initialBackoff` it must be generated here instead of
  67. // by `makeNextElement`.
  68. let backoff = min(connectionBackoff.initialBackoff, connectionBackoff.maximumBackoff)
  69. self.initialElement = self.makeElement(backoff: backoff)
  70. }
  71. /// The configuration being used.
  72. private let connectionBackoff: ConnectionBackoff
  73. /// The backoff in seconds, without jitter.
  74. private var unjitteredBackoff: TimeInterval
  75. /// The first element to return. Since the first backoff is defined as `initialBackoff` we can't
  76. /// compute it on-the-fly.
  77. private var initialElement: Element?
  78. /// Returns the next pair of connection timeout and backoff (in that order) to use should the
  79. /// connection attempt fail.
  80. public func next() -> Element? {
  81. if let initial = self.initialElement {
  82. self.initialElement = nil
  83. return initial
  84. } else {
  85. return self.makeNextElement()
  86. }
  87. }
  88. /// Produces the next element to return.
  89. private func makeNextElement() -> Element {
  90. let unjittered = self.unjitteredBackoff * self.connectionBackoff.multiplier
  91. self.unjitteredBackoff = min(unjittered, self.connectionBackoff.maximumBackoff)
  92. let backoff = self.jittered(value: self.unjitteredBackoff)
  93. return self.makeElement(backoff: backoff)
  94. }
  95. /// Make a timeout-backoff pair from the given backoff. The timeout is the `max` of the backoff
  96. /// and `connectionBackoff.minimumConnectionTimeout`.
  97. private func makeElement(backoff: TimeInterval) -> Element {
  98. let timeout = max(backoff, self.connectionBackoff.minimumConnectionTimeout)
  99. return (timeout, backoff)
  100. }
  101. /// Adds 'jitter' to the given value.
  102. private func jittered(value: TimeInterval) -> TimeInterval {
  103. let lower = -self.connectionBackoff.jitter * value
  104. let upper = self.connectionBackoff.jitter * value
  105. return value + TimeInterval.random(in: lower...upper)
  106. }
  107. }