Unverified Commit 61934da0 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add connection poisoning to aws-smithy-client (#2445)

* Add Connection Poisoning to aws-smithy-client

* Fix doc links

* Remove required tokio dependency from aws-smithy-client

* Remove external type exposed

* Rename, re-add tokio dependency

* Change IP to 127.0.0.1 to attempt to fix windows

* Add dns::Name to external types

* Remove non_exhaustive not needed

* Add client target to changelog
parent b2c5eaa3
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -287,3 +287,28 @@ message = "The modules in generated client crates have been reorganized. See the
references = ["smithy-rs#2448"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"

[[aws-sdk-rust]]
message = """Reconnect on transient errors.

If a transient error (timeout, 500, 503, 503) is encountered, the connection will be evicted from the pool and will not
be reused. This is enabled by default for all AWS services. It can be disabled by setting `RetryConfig::with_reconnect_mode`

Although there is no API breakage from this change, it alters the client behavior in a way that may cause breakage for customers.
"""
references = ["aws-sdk-rust#160", "smithy-rs#2445"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "rcoh"

[[smithy-rs]]
message = """Reconnect on transient errors.

Note: **this behavior is disabled by default for generic clients**. It can be enabled with
`aws_smithy_client::Builder::reconnect_on_transient_errors`

If a transient error (timeout, 500, 503, 503) is encountered, the connection will be evicted from the pool and will not
be reused.
"""
references = ["aws-sdk-rust#160", "smithy-rs#2445"]
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" }
author = "rcoh"
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ aws-smithy-types = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-types" }
aws-types = { path = "../../sdk/build/aws-sdk/sdk/aws-types" }
hyper = { version = "0.14.12", default-features = false }
time = { version = "0.3.4", features = ["parsing"] }
tokio = { version = "1.8.4", features = ["sync"] }
tokio = { version = "1.13.1", features = ["sync"] }
tracing = { version = "0.1" }

# implementation detail of SSO credential caching
+3 −1
Original line number Diff line number Diff line
@@ -208,6 +208,7 @@ private class AwsFluentClientExtensions(types: Types) {
                    };
                    let mut builder = builder
                        .middleware(#{DynMiddleware}::new(#{Middleware}::new()))
                        .reconnect_mode(retry_config.reconnect_mode())
                        .retry_config(retry_config.into())
                        .operation_timeout_config(timeout_config.into());
                    builder.set_sleep_impl(sleep_impl);
@@ -257,6 +258,7 @@ private fun renderCustomizableOperationSendMethod(
        "combined_generics_decl" to combinedGenerics.declaration(),
        "handle_generics_bounds" to handleGenerics.bounds(),
        "SdkSuccess" to RuntimeType.sdkSuccess(runtimeConfig),
        "SdkError" to RuntimeType.sdkError(runtimeConfig),
        "ClassifyRetry" to RuntimeType.classifyRetry(runtimeConfig),
        "ParseHttpResponse" to RuntimeType.parseHttpResponse(runtimeConfig),
    )
@@ -272,7 +274,7 @@ private fun renderCustomizableOperationSendMethod(
            where
                E: std::error::Error + Send + Sync + 'static,
                O: #{ParseHttpResponse}<Output = Result<T, E>> + Send + Sync + Clone + 'static,
                Retry: #{ClassifyRetry}<#{SdkSuccess}<T>, SdkError<E>> + Send + Sync + Clone,
                Retry: #{ClassifyRetry}<#{SdkSuccess}<T>, #{SdkError}<E>> + Send + Sync + Clone,
            {
                self.handle.client.call(self.operation).await
            }
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

use aws_credential_types::provider::SharedCredentialsProvider;
use aws_credential_types::Credentials;
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_client::test_connection::wire_mock::{
    check_matches, ReplayedEvent, WireLevelTestConnection,
};
use aws_smithy_client::{ev, match_events};
use aws_smithy_types::retry::{ReconnectMode, RetryConfig};
use aws_types::region::Region;
use aws_types::SdkConfig;
use std::sync::Arc;

#[tokio::test]
/// test that disabling reconnects on retry config disables them for the client
async fn disable_reconnects() {
    let mock = WireLevelTestConnection::spinup(vec![
        ReplayedEvent::status(503),
        ReplayedEvent::status(503),
        ReplayedEvent::with_body("here-is-your-object"),
    ])
    .await;

    let sdk_config = SdkConfig::builder()
        .region(Region::from_static("us-east-2"))
        .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
        .sleep_impl(Arc::new(TokioSleep::new()))
        .endpoint_url(mock.endpoint_url())
        .http_connector(mock.http_connector())
        .retry_config(
            RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReuseAllConnections),
        )
        .build();
    let client = aws_sdk_s3::Client::new(&sdk_config);
    let resp = client
        .get_object()
        .bucket("bucket")
        .key("key")
        .send()
        .await
        .expect("succeeds after retries");
    assert_eq!(
        resp.body.collect().await.unwrap().to_vec(),
        b"here-is-your-object"
    );
    match_events!(
        ev!(dns),
        ev!(connect),
        ev!(http(503)),
        ev!(http(503)),
        ev!(http(200))
    )(&mock.events());
}

#[tokio::test]
async fn reconnect_on_503() {
    let mock = WireLevelTestConnection::spinup(vec![
        ReplayedEvent::status(503),
        ReplayedEvent::status(503),
        ReplayedEvent::with_body("here-is-your-object"),
    ])
    .await;

    let sdk_config = SdkConfig::builder()
        .region(Region::from_static("us-east-2"))
        .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
        .sleep_impl(Arc::new(TokioSleep::new()))
        .endpoint_url(mock.endpoint_url())
        .http_connector(mock.http_connector())
        .retry_config(RetryConfig::standard())
        .build();
    let client = aws_sdk_s3::Client::new(&sdk_config);
    let resp = client
        .get_object()
        .bucket("bucket")
        .key("key")
        .send()
        .await
        .expect("succeeds after retries");
    assert_eq!(
        resp.body.collect().await.unwrap().to_vec(),
        b"here-is-your-object"
    );
    match_events!(
        ev!(dns),
        ev!(connect),
        ev!(http(503)),
        ev!(dns),
        ev!(connect),
        ev!(http(503)),
        ev!(dns),
        ev!(connect),
        ev!(http(200))
    )(&mock.events());
}
+5 −1
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ class CustomizableOperationGenerator(
                "Operation" to smithyHttp.resolve("operation::Operation"),
                "Request" to smithyHttp.resolve("operation::Request"),
                "Response" to smithyHttp.resolve("operation::Response"),
                "ClassifyRetry" to smithyHttp.resolve("retry::ClassifyRetry"),
                "ClassifyRetry" to RuntimeType.classifyRetry(runtimeConfig),
                "RetryKind" to smithyTypes.resolve("retry::RetryKind"),
            )
            renderCustomizableOperationModule(this)
@@ -150,6 +150,9 @@ class CustomizableOperationGenerator(
            "ParseHttpResponse" to smithyHttp.resolve("response::ParseHttpResponse"),
            "NewRequestPolicy" to smithyClient.resolve("retry::NewRequestPolicy"),
            "SmithyRetryPolicy" to smithyClient.resolve("bounds::SmithyRetryPolicy"),
            "ClassifyRetry" to RuntimeType.classifyRetry(runtimeConfig),
            "SdkSuccess" to RuntimeType.sdkSuccess(runtimeConfig),
            "SdkError" to RuntimeType.sdkError(runtimeConfig),
        )

        writer.rustTemplate(
@@ -164,6 +167,7 @@ class CustomizableOperationGenerator(
                    E: std::error::Error + Send + Sync + 'static,
                    O: #{ParseHttpResponse}<Output = Result<T, E>> + Send + Sync + Clone + 'static,
                    Retry: Send + Sync + Clone,
                    Retry: #{ClassifyRetry}<#{SdkSuccess}<T>, #{SdkError}<E>> + Send + Sync + Clone,
                    <R as #{NewRequestPolicy}>::Policy: #{SmithyRetryPolicy}<O, T, E, Retry> + Clone,
                {
                    self.handle.client.call(self.operation).await
Loading