diff --git a/aws-runtime/protocols/http/common/src/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicy.kt b/aws-runtime/protocols/http/common/src/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicy.kt new file mode 100644 index 00000000000..4e3a32978bb --- /dev/null +++ b/aws-runtime/protocols/http/common/src/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicy.kt @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package aws.sdk.kotlin.runtime.http.retries + +import aws.sdk.kotlin.runtime.AwsServiceException +import aws.smithy.kotlin.runtime.ServiceErrorMetadata +import aws.smithy.kotlin.runtime.http.response.HttpResponse +import aws.smithy.kotlin.runtime.retries.RetryDirective +import aws.smithy.kotlin.runtime.retries.RetryErrorType.* +import aws.smithy.kotlin.runtime.retries.impl.StandardRetryPolicy + +public object AwsDefaultRetryPolicy : StandardRetryPolicy() { + internal val knownErrorTypes = mapOf( + "BandwidthLimitExceeded" to Throttling, + "EC2ThrottledException" to Throttling, + "IDPCommunicationError" to Timeout, + "LimitExceededException" to Throttling, + "PriorRequestNotComplete" to Throttling, + "ProvisionedThroughputExceededException" to Throttling, + "RequestLimitExceeded" to Throttling, + "RequestThrottled" to Throttling, + "RequestThrottledException" to Throttling, + "RequestTimeout" to Timeout, + "RequestTimeoutException" to Timeout, + "SlowDown" to Throttling, + "ThrottledException" to Throttling, + "Throttling" to Throttling, + "ThrottlingException" to Throttling, + "TooManyRequestsException" to Throttling, + "TransactionInProgressException" to Throttling, + ) + + internal val knownStatusCodes = mapOf( + 500 to Timeout, + 502 to Timeout, + 503 to Timeout, + 504 to Timeout, + ) + + override fun evaluateOtherExceptions(ex: Throwable): RetryDirective? = when (ex) { + is AwsServiceException -> evaluateAwsServiceException(ex) + else -> null + } + + private fun evaluateAwsServiceException(ex: AwsServiceException): RetryDirective? = with(ex.sdkErrorMetadata) { + (knownErrorTypes[errorCode] ?: knownStatusCodes[statusCode]) + ?.let { RetryDirective.RetryError(it) } + } + + private val ServiceErrorMetadata.statusCode: Int? + get() = (protocolResponse as? HttpResponse)?.status?.value +} diff --git a/aws-runtime/protocols/http/common/test/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicyTest.kt b/aws-runtime/protocols/http/common/test/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicyTest.kt new file mode 100644 index 00000000000..4adfc4b2ca4 --- /dev/null +++ b/aws-runtime/protocols/http/common/test/aws/sdk/kotlin/runtime/http/retries/AwsDefaultRetryPolicyTest.kt @@ -0,0 +1,36 @@ +package aws.sdk.kotlin.runtime.http.retries + +import aws.sdk.kotlin.runtime.AwsErrorMetadata +import aws.sdk.kotlin.runtime.AwsServiceException +import aws.smithy.kotlin.runtime.ServiceErrorMetadata +import aws.smithy.kotlin.runtime.http.Headers +import aws.smithy.kotlin.runtime.http.HttpBody +import aws.smithy.kotlin.runtime.http.HttpStatusCode +import aws.smithy.kotlin.runtime.http.response.HttpResponse +import aws.smithy.kotlin.runtime.retries.RetryDirective +import kotlin.test.Test +import kotlin.test.assertEquals + +class AwsDefaultRetryPolicyTest { + @Test + fun testErrorsByErrorCode() { + AwsDefaultRetryPolicy.knownErrorTypes.forEach { (errorCode, errorType) -> + val ex = AwsServiceException() + ex.sdkErrorMetadata.attributes[AwsErrorMetadata.ErrorCode] = errorCode + val result = AwsDefaultRetryPolicy.evaluate(Result.failure(ex)) + assertEquals(RetryDirective.RetryError(errorType), result) + } + } + + @Test + fun testErrorsByStatusCode() { + AwsDefaultRetryPolicy.knownStatusCodes.forEach { (statusCode, errorType) -> + val modeledStatusCode = HttpStatusCode.fromValue(statusCode) + val response = HttpResponse(modeledStatusCode, Headers.Empty, HttpBody.Empty) + val ex = AwsServiceException() + ex.sdkErrorMetadata.attributes[ServiceErrorMetadata.ProtocolResponse] = response + val result = AwsDefaultRetryPolicy.evaluate(Result.failure(ex)) + assertEquals(RetryDirective.RetryError(errorType), result) + } + } +} diff --git a/docs/design/README.md b/docs/design/README.md index 0cbcf0edbf5..565df41b8ed 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -9,3 +9,4 @@ These designs extend or augment the [Smithy Kotlin Designs](https://github.com/a ## Detailed sub-designs * [Endpoint resolution](endpoint-resolution.md) +* [SDK-specific Retries](retries.md) diff --git a/docs/design/retries.md b/docs/design/retries.md new file mode 100644 index 00000000000..55f7d7ca1f2 --- /dev/null +++ b/docs/design/retries.md @@ -0,0 +1,47 @@ +# SDK-specific Retry Design + +* **Type**: Design +* **Author(s)**: Ian Botsford + +# Abstract + +The AWS SDK for Kotlin uses a specialization of the generalized +[**smithy-kotlin** Retry Design](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/retries.md). This +document covers those specializations (but does not re-hash the generalized design). + +# SDK implementation + +The SDK uses the following customizations/specializations over the generalized +[**smithy-kotlin** Retry Design](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/retries.md): + +## Retry policy + +The generalized `StandardRetryPolicy` is subclassed to provide support for information only available in AWS-specific +exception types: + +```kotlin +object AwsDefaultRetryPolicy : StandardRetryPolicy() { + internal val knownErrorTypes = mapOf( + "BandwidthLimitExceeded" to Throttling, + "RequestTimeoutException" to Timeout, + "TooManyRequestsException" to Throttling, + … + ) + + override fun evaluateOtherExceptions(ex: Throwable): RetryDirective? = when (ex) { + is AwsServiceException -> evaluateAwsServiceException(ex) + else -> null + } + + private fun evaluateAwsServiceException(ex: AwsServiceException): RetryDirective? = with(ex.sdkErrorMetadata) { + knownErrorTypes[errorCode]?.let { RetryDirective.RetryError(it) } + } +} +``` + +This policy utilizes the error code provided in the exception to derive a retry directive based on a known list. This +list may grow/change over time. + +# Revision history + +* 9/27/2021 - Created