From fd802edaa1436837138956b010366dff2f08dd56 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Wed, 29 Sep 2021 15:51:10 +0200 Subject: [PATCH 1/3] prepare `calculateBackoff(failedAttempt:)` to be used in `HTTP2StateMaschine` too --- .../HTTPConnectionPool+Backoff.swift | 55 +++++++++++++++++++ ...HTTPConnectionPool+HTTP1StateMachine.swift | 26 +-------- 2 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift new file mode 100644 index 000000000..aed624baf --- /dev/null +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// 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 NIO + +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 backoffNanoseconds = Int64(start * pow(1.25, Double(attempts - 1))) + + let backoff: TimeAmount = min(.nanoseconds(backoffNanoseconds), .seconds(60)) + + // Calculate a 3% jitter range + let jitterRange = (backoff.nanoseconds / 100) * 3 + // Pick a random element from the range +/- jitter range. + let jitter: TimeAmount = .nanoseconds((-jitterRange...jitterRange).randomElement()!) + let jitteredBackoff = backoff + jitter + return jitteredBackoff + } +} diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift index 3695b3c90..f73521bfb 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift @@ -154,7 +154,7 @@ extension HTTPConnectionPool { // decision about the retry will be made in `connectionCreationBackoffDone(_:)` let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID) - let backoff = self.calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts) + let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts) return .init( request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop) @@ -444,30 +444,6 @@ extension HTTPConnectionPool { self.connections.removeConnection(at: index) return .none } - - private 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 backoffNanoseconds = Int64(start * pow(1.25, Double(attempts - 1))) - - let backoff: TimeAmount = min(.nanoseconds(backoffNanoseconds), .seconds(60)) - - // Calculate a 3% jitter range - let jitterRange = (backoff.nanoseconds / 100) * 3 - // Pick a random element from the range +/- jitter range. - let jitter: TimeAmount = .nanoseconds((-jitterRange...jitterRange).randomElement()!) - let jitteredBackoff = backoff + jitter - return jitteredBackoff - } } } From 76719a11aaa5e7f9d626752d973c240cd7b91809 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Wed, 29 Sep 2021 16:30:50 +0200 Subject: [PATCH 2/3] import only `NIOCore` and `pow` function from platform --- .../State Machine/HTTPConnectionPool+Backoff.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift index aed624baf..cdb1c7b4b 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift @@ -12,7 +12,12 @@ // //===----------------------------------------------------------------------===// -import NIO +import NIOCore +#if canImport(Darwin) +import func Darwin.pow +#else +import func Glibc.pow +#endif extension HTTPConnectionPool { /// Calculates the delay for the next connection attempt after the given number of failed `attempts`. From 09e29430b30c260895d50983ea8f356445d97f66 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Wed, 29 Sep 2021 16:36:33 +0200 Subject: [PATCH 3/3] fix formatting --- .../State Machine/HTTPConnectionPool+Backoff.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift index cdb1c7b4b..7a356ce2b 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift @@ -14,9 +14,9 @@ import NIOCore #if canImport(Darwin) -import func Darwin.pow + import func Darwin.pow #else -import func Glibc.pow + import func Glibc.pow #endif extension HTTPConnectionPool {