|
|
@@ -0,0 +1,87 @@
|
|
|
+/*
|
|
|
+ * Copyright 2024, gRPC Authors All rights reserved.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+
|
|
|
+@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
|
|
|
+struct ConnectionBackoff {
|
|
|
+ var initial: Duration
|
|
|
+ var max: Duration
|
|
|
+ var multiplier: Double
|
|
|
+ var jitter: Double
|
|
|
+
|
|
|
+ init(initial: Duration, max: Duration, multiplier: Double, jitter: Double) {
|
|
|
+ self.initial = initial
|
|
|
+ self.max = max
|
|
|
+ self.multiplier = multiplier
|
|
|
+ self.jitter = jitter
|
|
|
+ }
|
|
|
+
|
|
|
+ func makeIterator() -> Iterator {
|
|
|
+ return Iterator(self)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Deliberately not conforming to `IteratorProtocol` as `next()` never returns `nil` which
|
|
|
+ // isn't expressible via `IteratorProtocol`.
|
|
|
+ struct Iterator {
|
|
|
+ private var isInitial: Bool
|
|
|
+ private var currentBackoffSeconds: Double
|
|
|
+
|
|
|
+ private let jitter: Double
|
|
|
+ private let multiplier: Double
|
|
|
+ private let maxBackoffSeconds: Double
|
|
|
+
|
|
|
+ init(_ backoff: ConnectionBackoff) {
|
|
|
+ self.isInitial = true
|
|
|
+ self.currentBackoffSeconds = Self.seconds(from: backoff.initial)
|
|
|
+ self.jitter = backoff.jitter
|
|
|
+ self.multiplier = backoff.multiplier
|
|
|
+ self.maxBackoffSeconds = Self.seconds(from: backoff.max)
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func seconds(from duration: Duration) -> Double {
|
|
|
+ var seconds = Double(duration.components.seconds)
|
|
|
+ seconds += Double(duration.components.attoseconds) / 1e18
|
|
|
+ return seconds
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func duration(from seconds: Double) -> Duration {
|
|
|
+ let nanoseconds = seconds * 1e9
|
|
|
+ let wholeNanos = Int64(nanoseconds)
|
|
|
+ return .nanoseconds(wholeNanos)
|
|
|
+ }
|
|
|
+
|
|
|
+ mutating func next() -> Duration {
|
|
|
+ // The initial backoff doesn't get jittered.
|
|
|
+ if self.isInitial {
|
|
|
+ self.isInitial = false
|
|
|
+ return Self.duration(from: self.currentBackoffSeconds)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Scale up the last backoff.
|
|
|
+ self.currentBackoffSeconds *= self.multiplier
|
|
|
+
|
|
|
+ // Limit it to the max backoff.
|
|
|
+ if self.currentBackoffSeconds > self.maxBackoffSeconds {
|
|
|
+ self.currentBackoffSeconds = self.maxBackoffSeconds
|
|
|
+ }
|
|
|
+
|
|
|
+ let backoff = self.currentBackoffSeconds
|
|
|
+ let jitter = Double.random(in: -(self.jitter * backoff) ... self.jitter * backoff)
|
|
|
+ let jitteredBackoff = backoff + jitter
|
|
|
+
|
|
|
+ return Self.duration(from: jitteredBackoff)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|