Skip to content

Commit

Permalink
feat!: Create retry modules (#710)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbelkins authored May 24, 2024
1 parent 6c5c0f0 commit fdc7a1b
Show file tree
Hide file tree
Showing 48 changed files with 255 additions and 83 deletions.
8 changes: 1 addition & 7 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ excluded:
- .build
- Sources/SmithyTestUtil/*
- Sources/WeatherSDK/*
- Tests/ClientRuntimeTests/*
- Tests/SmithyTestUtilTests/*
- Tests/SmithyXMLTests/*
- Tests/SmithyJSONTests/*
- Tests/SmithyFormURLTests/*
- Tests/SmithyTimestampsTests/*
- Tests/WeatherSDKTests/*
- Tests/*
- smithy-swift-codegen-test/build/*

analyzer_rules:
Expand Down
21 changes: 19 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ let package = Package(
],
products: [
.library(name: "ClientRuntime", targets: ["ClientRuntime"]),
.library(name: "SmithyRetriesAPI", targets: ["SmithyRetriesAPI"]),
.library(name: "SmithyRetries", targets: ["SmithyRetries"]),
.library(name: "SmithyReadWrite", targets: ["SmithyReadWrite"]),
.library(name: "SmithyXML", targets: ["SmithyXML"]),
.library(name: "SmithyJSON", targets: ["SmithyJSON"]),
Expand All @@ -43,6 +45,8 @@ let package = Package(
.target(
name: "ClientRuntime",
dependencies: [
"SmithyRetriesAPI",
"SmithyRetries",
"SmithyXML",
"SmithyJSON",
"SmithyFormURL",
Expand All @@ -53,6 +57,19 @@ let package = Package(
.copy("PrivacyInfo.xcprivacy")
]
),
.target(
name: "SmithyRetriesAPI"
),
.target(
name: "SmithyRetries",
dependencies: [
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"),
]
),
.testTarget(
name: "SmithyRetriesTests",
dependencies: ["ClientRuntime", "SmithyRetriesAPI", "SmithyRetries"]
),
.target(
name: "SmithyReadWrite",
dependencies: [
Expand Down Expand Up @@ -121,11 +138,11 @@ func addTestServiceTargets() {
package.targets += [
.target(
name: "WeatherSDK",
dependencies: ["SmithyTestUtil", "ClientRuntime"]
dependencies: ["SmithyTestUtil", "ClientRuntime", "SmithyRetriesAPI", "SmithyRetries"]
),
.testTarget(
name: "WeatherSDKTests",
dependencies: ["WeatherSDK"]
dependencies: ["WeatherSDK", "SmithyTestUtil"]
)
]
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/ClientRuntime/Config/DefaultClientConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import struct SmithyRetriesAPI.RetryStrategyOptions

public protocol DefaultClientConfiguration: ClientConfiguration {
/// The configuration for retry of failed network requests.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
// SPDX-License-Identifier: Apache-2.0
//

import protocol SmithyRetriesAPI.RetryStrategy
import protocol SmithyRetriesAPI.RetryErrorInfoProvider
import struct SmithyRetriesAPI.RetryStrategyOptions
import struct SmithyRetries.DefaultRetryStrategy
import struct SmithyRetries.ExponentialBackoffStrategy

/// Provides configuration options for a Smithy-based service.
public struct DefaultSDKRuntimeConfiguration<DefaultSDKRuntimeRetryStrategy: RetryStrategy,
DefaultSDKRuntimeRetryErrorInfoProvider: RetryErrorInfoProvider> {
Expand Down Expand Up @@ -107,7 +113,9 @@ public extension DefaultSDKRuntimeConfiguration {
/// The retry strategy options to use when none is provided.
///
/// Defaults to options with the defaults defined in `RetryStrategyOptions`.
static var defaultRetryStrategyOptions: RetryStrategyOptions { RetryStrategyOptions() }
static var defaultRetryStrategyOptions: RetryStrategyOptions {
RetryStrategyOptions(backoffStrategy: ExponentialBackoffStrategy())
}

/// The log mode to use when none is provided
///
Expand Down
19 changes: 10 additions & 9 deletions Sources/ClientRuntime/Middleware/RetryMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import struct Foundation.Locale
import struct Foundation.TimeInterval
import struct Foundation.TimeZone
import struct Foundation.UUID
import protocol SmithyRetriesAPI.RetryStrategy
import protocol SmithyRetriesAPI.RetryErrorInfoProvider
import struct SmithyRetriesAPI.RetryStrategyOptions

public struct RetryMiddleware<Strategy: RetryStrategy,
ErrorInfoProvider: RetryErrorInfoProvider,
Expand Down Expand Up @@ -64,17 +67,15 @@ public struct RetryMiddleware<Strategy: RetryStrategy,
context.getLogger()?.error("Failed to refresh retry token: \(errorInfo.errorType)")
throw operationError
}
var estimatedSkew = context.attributes.get(key: AttributeKeys.estimatedSkew)
if estimatedSkew == nil {
estimatedSkew = 0
var estimatedSkew = context.attributes.get(key: AttributeKeys.estimatedSkew) ?? {

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=OS X)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=OS X)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=tvOS Simulator,OS=16.1,name=Apple TV 4K (3rd ge...

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=tvOS Simulator,OS=16.1,name=Apple TV 4K (3rd ge...

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=OS X)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=tvOS Simulator,OS=17.5,name=Apple TV 4K (3rd ge...

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=iOS Simulator,OS=16.1,name=iPhone 14)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=iOS Simulator,OS=16.1,name=iPhone 14)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant

Check warning on line 70 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=iOS Simulator,OS=17.5,name=iPhone 15)

variable 'estimatedSkew' was never mutated; consider changing to 'let' constant
context.getLogger()?.info("Estimated skew not found; defaulting to zero.")
}
var socketTimeout = context.attributes.get(key: AttributeKeys.socketTimeout)
if socketTimeout == nil {
socketTimeout = 60.0
return 0
}()
var socketTimeout = context.attributes.get(key: AttributeKeys.socketTimeout) ?? {

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=OS X)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=OS X)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=tvOS Simulator,OS=16.1,name=Apple TV 4K (3rd ge...

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=tvOS Simulator,OS=16.1,name=Apple TV 4K (3rd ge...

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=OS X)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=tvOS Simulator,OS=17.5,name=Apple TV 4K (3rd ge...

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=iOS Simulator,OS=16.1,name=iPhone 14)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-13-xlarge, Xcode_14.1, platform=iOS Simulator,OS=16.1,name=iPhone 14)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant

Check warning on line 74 in Sources/ClientRuntime/Middleware/RetryMiddleware.swift

View workflow job for this annotation

GitHub Actions / downstream (macos-14-xlarge, Xcode_15.4, platform=iOS Simulator,OS=17.5,name=iPhone 15)

variable 'socketTimeout' was never mutated; consider changing to 'let' constant
context.getLogger()?.info("Socket timeout value not found; defaulting to 60 seconds.")
}
let ttlDateUTCString = getTTL(now: Date(), estimatedSkew: estimatedSkew!, socketTimeout: socketTimeout!)
return 60.0
}()
let ttlDateUTCString = getTTL(now: Date(), estimatedSkew: estimatedSkew, socketTimeout: socketTimeout)
input.headers.update(
name: "amz-sdk-request",
value: "ttl=\(ttlDateUTCString); attempt=\(attemptNumber + 1); max=\(maxRetries)"
Expand Down
3 changes: 3 additions & 0 deletions Sources/ClientRuntime/Orchestrator/Orchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// SPDX-License-Identifier: Apache-2.0
//

import protocol SmithyRetriesAPI.RetryStrategy
import struct SmithyRetriesAPI.RetryErrorInfo

/// Orchestrates operation execution
///
/// Execution performs the following steps in order:
Expand Down
3 changes: 3 additions & 0 deletions Sources/ClientRuntime/Orchestrator/OrchestratorBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// SPDX-License-Identifier: Apache-2.0
//

import protocol SmithyRetriesAPI.RetryStrategy
import struct SmithyRetriesAPI.RetryErrorInfo

/// Builds an Orchestrator, combining runtime components, interceptors, serializers, and deserializers.
///
/// Note: This is intended to be used within generated code, not directly.
Expand Down
2 changes: 2 additions & 0 deletions Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import struct SmithyRetries.DefaultRetryStrategy

public class DefaultClientPlugin: Plugin {
public init() {}
public func configureClient(clientConfiguration: ClientConfiguration) {
Expand Down
2 changes: 2 additions & 0 deletions Sources/ClientRuntime/Plugins/HttpClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import struct SmithyRetries.DefaultRetryStrategy

public class DefaultHttpClientPlugin: Plugin {

var httpClientConfiguration: HttpClientConfiguration
Expand Down
2 changes: 2 additions & 0 deletions Sources/ClientRuntime/Plugins/RetryPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import struct SmithyRetriesAPI.RetryStrategyOptions

public class RetryPlugin: Plugin {

private var retryStrategyOptions: RetryStrategyOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
//

import struct Foundation.TimeInterval
import struct SmithyRetriesAPI.RetryErrorInfo
import enum SmithyRetriesAPI.RetryErrorType
import protocol SmithyRetriesAPI.RetryErrorInfoProvider

public enum DefaultRetryErrorInfoProvider: RetryErrorInfoProvider {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

public extension DefaultRetryStrategy {

/// Errors that may be thrown when an operation is retried unsuccessfully.
enum Error: Swift.Error {

Check warning on line 12 in Sources/SmithyRetries/DefaultRetryStrategy/DefaultRetryStrategy+Error.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Lines should not have trailing whitespace (trailing_whitespace)
/// The number and frequency of retries being attempted has exceeded the
/// current limits.
///
/// This error is only raised when the `adaptive` rate limiting mode of retry is used.
case insufficientQuota
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
//

import struct Foundation.TimeInterval
import protocol SmithyRetriesAPI.RetryStrategy
import struct SmithyRetriesAPI.RetryStrategyOptions
import struct SmithyRetriesAPI.RetryErrorInfo
import enum SmithyRetriesAPI.RetryError

public struct DefaultRetryStrategy: RetryStrategy {
public typealias Token = DefaultRetryToken
Expand Down Expand Up @@ -43,7 +47,7 @@ public struct DefaultRetryStrategy: RetryStrategy {
if let capacityAmount = await tokenToRenew.quota.hasRetryQuota(isTimeout: errorInfo.isTimeout) {
tokenToRenew.capacityAmount = capacityAmount
} else {
throw RetryError.insufficientQuota
throw Error.insufficientQuota
}
let isThrottling = errorInfo.errorType == .throttling
await tokenToRenew.quota.updateClientSendingRate(isThrottling: isThrottling)
Expand All @@ -55,8 +59,3 @@ public struct DefaultRetryStrategy: RetryStrategy {
await token.quota.retryQuotaRelease(isSuccess: true, capacityAmount: token.capacityAmount)
}
}

enum RetryError: Error {
case maxAttemptsReached
case insufficientQuota
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import struct Foundation.TimeInterval
import protocol SmithyRetriesAPI.RetryToken

/// A token that is used to track retry of operations.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import struct Foundation.TimeInterval
import struct SmithyRetriesAPI.RetryStrategyOptions

/// Keeps the retry quota count for one partition ID.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import struct SmithyRetriesAPI.RetryStrategyOptions

/// Holds multiple quotas, keyed by partition IDs.
actor RetryQuotaRepository {
let options: RetryStrategyOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import func Foundation.pow
import struct Foundation.TimeInterval
import protocol SmithyRetriesAPI.RetryBackoffStrategy

public struct ExponentialBackoffStrategy: RetryBackoffStrategy {
let options: ExponentialBackoffStrategyOptions
Expand Down
21 changes: 21 additions & 0 deletions Sources/SmithyRetries/RetryErrorType+CRT.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import enum SmithyRetriesAPI.RetryErrorType
import AwsCommonRuntimeKit

public extension RetryErrorType {

func toCRTType() -> AwsCommonRuntimeKit.RetryError {
switch self {
case .transient: return .transient
case .throttling: return .throttling
case .serverError: return .serverError
case .clientError: return .clientError
}
}
}
14 changes: 14 additions & 0 deletions Sources/SmithyRetriesAPI/RetryError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

/// Errors that may be thrown when an operation is retried unsuccessfully.
public enum RetryError: Error {

/// The maximum number of allowed retry attempts were made,
/// but the operation could not be successfully completed.
case maxAttemptsReached
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

import struct Foundation.TimeInterval

/// A set of information fields that are derived from an error thrown when connecting to a service.
///
/// The `RetryErrorInfoProvider` creates an instance of this structure, which is then used by the
/// `RetryStrategy` to determine whether & how to retry a failed attempt.
public struct RetryErrorInfo: Equatable {

/// The general nature of the cause of this retryable error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
//
// SPDX-License-Identifier: Apache-2.0
//
import AwsCommonRuntimeKit

/// This enum is a general classification that is assigned to errors that resulted from a failed attempt to perform
/// an operation.
///
/// The retry strategy uses this classification to decide whether and when to retry a failed attempt.
public enum RetryErrorType: Equatable {

/// This is a connection level error such as a socket timeout, socket connect error, tls negotiation timeout etc...
Expand All @@ -22,15 +25,3 @@ public enum RetryErrorType: Equatable {
/// Doesn’t count against any budgets. This could be something like a 401 challenge in HTTP.
case clientError
}

public extension RetryErrorType {

func toCRTType() -> AwsCommonRuntimeKit.RetryError {
switch self {
case .transient: return .transient
case .throttling: return .throttling
case .serverError: return .serverError
case .clientError: return .clientError
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public struct RetryStrategyOptions {
/// Sets the initial available capacity for this retry strategy's quotas.
///
/// Used only during testing, production uses the default values.
let availableCapacity: Int
public let availableCapacity: Int

/// Sets the maximum capacity for this retry strategy's quotas.
///
/// Used only during testing, production uses the default values.
let maxCapacity: Int
public let maxCapacity: Int

/// Creates a new set of retry strategy options
/// - Parameters:
Expand All @@ -50,7 +50,7 @@ public struct RetryStrategyOptions {
/// - availableCapacity: The number of available tokens in a retry quota. Defaults to 500.
/// - maxCapacity: The max number of tokens in a retry quota. Defaults to 500.
public init(
backoffStrategy: RetryBackoffStrategy = ExponentialBackoffStrategy(),
backoffStrategy: RetryBackoffStrategy,
maxRetriesBase: Int = 2,
availableCapacity: Int = 500,
maxCapacity: Int = 500,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
// SPDX-License-Identifier: Apache-2.0
//

import protocol SmithyRetriesAPI.RetryStrategy
import protocol SmithyRetriesAPI.RetryToken
import struct SmithyRetriesAPI.RetryStrategyOptions
import struct SmithyRetriesAPI.RetryErrorInfo
import enum SmithyRetriesAPI.RetryError

@testable import ClientRuntime

public struct MockRetryStrategy: RetryStrategy {
Expand Down
Loading

0 comments on commit fdc7a1b

Please sign in to comment.