//===----------------------------------------------------------------------===// // // This source file is part of the AsyncHTTPClient open source project // // Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information // See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors // // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// import NIOCore #if canImport(Darwin) import func Darwin.pow #elseif canImport(Musl) import func Musl.pow #elseif canImport(Android) import func Android.pow #else import func Glibc.pow #endif extension HTTPConnectionPool { /// Calculates the delay for the next connection attempt after the given number of failed `attempts`. /// /// Our backoff formula is: 100ms * 1.25^(attempts - 1) that is capped of at 1 minute. /// This means for: /// - 1 failed attempt : 100ms /// - 5 failed attempts: ~300ms /// - 10 failed attempts: ~930ms /// - 15 failed attempts: ~2.84s /// - 20 failed attempts: ~8.67s /// - 25 failed attempts: ~26s /// - 29 failed attempts: ~60s (max out) /// /// - Parameter attempts: number of failed attempts in a row /// - Returns: time to wait until trying to establishing a new connection static func calculateBackoff(failedAttempt attempts: Int) -> TimeAmount { // Our backoff formula is: 100ms * 1.25^(attempts - 1) that is capped of at 1minute // This means for: // - 1 failed attempt : 100ms // - 5 failed attempts: ~300ms // - 10 failed attempts: ~930ms // - 15 failed attempts: ~2.84s // - 20 failed attempts: ~8.67s // - 25 failed attempts: ~26s // - 29 failed attempts: ~60s (max out) let start = Double(TimeAmount.milliseconds(100).nanoseconds) let backoffNanosecondsDouble = start * pow(1.25, Double(attempts - 1)) // Cap to 60s _before_ we convert to Int64, to avoid trapping in the Int64 initializer. let backoffNanoseconds = Int64(min(backoffNanosecondsDouble, Double(TimeAmount.seconds(60).nanoseconds))) let backoff = TimeAmount.nanoseconds(backoffNanoseconds) // Calculate a 3% jitter range let jitterRange = (backoff.nanoseconds / 100) * 3 // Pick a random element from the range +/- jitter range. let jitter: TimeAmount = .nanoseconds(Int64.random(in: -jitterRange...jitterRange)) let jitteredBackoff = backoff + jitter return jitteredBackoff } }