Skip to content

Commit 386ded8

Browse files
Zelda Hesslerjdisanti
andauthored
add connection poisoning support to the orchestrator (#2824)
This PR updates the orchestrator to support connection poisoning for the `hyper` connector. It also renames many usages of "connection" to "connector" in order to make talking about these concepts less confusing. In short: ``` /// A "connector" manages one or more "connections", handles connection timeouts, re-establishes /// connections, etc. /// /// "Connections" refers to the actual transport layer implementation of the connector. /// By default, the orchestrator uses a connector provided by `hyper`. ``` One possibly controversial decision I made is that `reconnect_mode` is now a standalone configurable field, rather that a sub-field of `RetryConfig`. I think we should get together as a team and discuss what kinds of things we want to allow users to do when configuring connections. My thoughts on this are that we should either: - **A.** Make connection-related settings their own types instead of including them within other config types - **B.** Make a single config struct for all connector-related stuff/use the preëxisting `ConnectorSettings` struct. Personally, I'm partial to A. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --------- Co-authored-by: John DiSanti <jdisanti@amazon.com>
1 parent 71b401f commit 386ded8

File tree

24 files changed

+336
-135
lines changed

24 files changed

+336
-135
lines changed

aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RetryClassifierDecorator.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
1515
import software.amazon.smithy.rust.codegen.core.rustlang.writable
1616
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
1717
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
18+
import software.amazon.smithy.rust.codegen.core.util.letIf
1819

1920
class RetryClassifierDecorator : ClientCodegenDecorator {
2021
override val name: String = "RetryPolicy"
@@ -25,10 +26,12 @@ class RetryClassifierDecorator : ClientCodegenDecorator {
2526
operation: OperationShape,
2627
baseCustomizations: List<OperationCustomization>,
2728
): List<OperationCustomization> =
28-
baseCustomizations + RetryClassifierFeature(codegenContext.runtimeConfig) + OperationRetryClassifiersFeature(
29-
codegenContext,
30-
operation,
31-
)
29+
(baseCustomizations + RetryClassifierFeature(codegenContext.runtimeConfig)).letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) {
30+
it + OperationRetryClassifiersFeature(
31+
codegenContext,
32+
operation,
33+
)
34+
}
3235
}
3336

3437
class RetryClassifierFeature(private val runtimeConfig: RuntimeConfig) : OperationCustomization() {

aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/timestream/TimestreamDecorator.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class TimestreamDecorator : ClientCodegenDecorator {
5252
Visibility.PUBLIC,
5353
CargoDependency.Tokio.copy(scope = DependencyScope.Compile, features = setOf("sync")),
5454
)
55-
val runtimeMode = codegenContext.smithyRuntimeMode
5655
rustCrate.lib {
5756
// helper function to resolve an endpoint given a base client
5857
rustTemplate(

aws/sdk/integration-tests/s3/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ publish = false
1212

1313
[dev-dependencies]
1414
async-std = "1.12.0"
15-
aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] }
1615
aws-config = { path = "../../build/aws-sdk/sdk/aws-config" }
16+
aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] }
1717
aws-http = { path = "../../build/aws-sdk/sdk/aws-http" }
1818
aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3" }
1919
aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" }
@@ -36,9 +36,9 @@ serde_json = "1"
3636
smol = "1.2"
3737
tempfile = "3"
3838
tokio = { version = "1.23.1", features = ["macros", "test-util", "rt-multi-thread"] }
39-
# If you're writing a test with this, take heed! `no-env-filter` means you'll be capturing
40-
# logs from everything that speaks, so be specific with your asserts.
41-
tracing-test = { version = "0.2.4", features = ["no-env-filter"] }
4239
tracing = "0.1.37"
4340
tracing-appender = "0.2.2"
4441
tracing-subscriber = { version = "0.3.15", features = ["env-filter", "json"] }
42+
# If you're writing a test with this, take heed! `no-env-filter` means you'll be capturing
43+
# logs from everything that speaks, so be specific with your asserts.
44+
tracing-test = { version = "0.2.4", features = ["no-env-filter"] }

aws/sdk/integration-tests/s3/tests/reconnects.rs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,34 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
use aws_credential_types::provider::SharedCredentialsProvider;
7-
use aws_credential_types::Credentials;
8-
use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep};
6+
use aws_sdk_s3::config::retry::{ReconnectMode, RetryConfig};
7+
use aws_sdk_s3::config::{Credentials, Region, SharedAsyncSleep};
8+
use aws_smithy_async::rt::sleep::TokioSleep;
99
use aws_smithy_client::test_connection::wire_mock::{
1010
check_matches, ReplayedEvent, WireLevelTestConnection,
1111
};
1212
use aws_smithy_client::{ev, match_events};
13-
use aws_smithy_types::retry::{ReconnectMode, RetryConfig};
14-
use aws_types::region::Region;
15-
use aws_types::SdkConfig;
1613

1714
#[tokio::test]
18-
/// test that disabling reconnects on retry config disables them for the client
19-
async fn disable_reconnects() {
15+
async fn test_disable_reconnect_on_503() {
2016
let mock = WireLevelTestConnection::spinup(vec![
2117
ReplayedEvent::status(503),
2218
ReplayedEvent::status(503),
2319
ReplayedEvent::with_body("here-is-your-object"),
2420
])
2521
.await;
2622

27-
let sdk_config = SdkConfig::builder()
23+
let config = aws_sdk_s3::Config::builder()
2824
.region(Region::from_static("us-east-2"))
29-
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
25+
.credentials_provider(Credentials::for_tests())
3026
.sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
3127
.endpoint_url(mock.endpoint_url())
3228
.http_connector(mock.http_connector())
3329
.retry_config(
3430
RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReuseAllConnections),
3531
)
3632
.build();
37-
let client = aws_sdk_s3::Client::new(&sdk_config);
33+
let client = aws_sdk_s3::Client::from_conf(config);
3834
let resp = client
3935
.get_object()
4036
.bucket("bucket")
@@ -56,23 +52,25 @@ async fn disable_reconnects() {
5652
}
5753

5854
#[tokio::test]
59-
async fn reconnect_on_503() {
55+
async fn test_enabling_reconnect_on_503() {
6056
let mock = WireLevelTestConnection::spinup(vec![
6157
ReplayedEvent::status(503),
6258
ReplayedEvent::status(503),
6359
ReplayedEvent::with_body("here-is-your-object"),
6460
])
6561
.await;
6662

67-
let sdk_config = SdkConfig::builder()
63+
let config = aws_sdk_s3::Config::builder()
6864
.region(Region::from_static("us-east-2"))
69-
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
65+
.credentials_provider(Credentials::for_tests())
7066
.sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
7167
.endpoint_url(mock.endpoint_url())
7268
.http_connector(mock.http_connector())
73-
.retry_config(RetryConfig::standard())
69+
.retry_config(
70+
RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReconnectOnTransientError),
71+
)
7472
.build();
75-
let client = aws_sdk_s3::Client::new(&sdk_config);
73+
let client = aws_sdk_s3::Client::from_conf(config);
7674
let resp = client
7775
.get_object()
7876
.bucket("bucket")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.client.smithy.customizations
7+
8+
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
9+
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization
10+
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection
11+
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
12+
import software.amazon.smithy.rust.codegen.core.rustlang.rust
13+
import software.amazon.smithy.rust.codegen.core.rustlang.writable
14+
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.smithyRuntime
15+
16+
class ConnectionPoisoningRuntimePluginCustomization(
17+
codegenContext: ClientCodegenContext,
18+
) : ServiceRuntimePluginCustomization() {
19+
private val runtimeConfig = codegenContext.runtimeConfig
20+
21+
override fun section(section: ServiceRuntimePluginSection): Writable = writable {
22+
when (section) {
23+
is ServiceRuntimePluginSection.RegisterInterceptor -> {
24+
// This interceptor assumes that a compatible Connector is set. Otherwise, connection poisoning
25+
// won't work and an error message will be logged.
26+
section.registerInterceptor(runtimeConfig, this) {
27+
rust(
28+
"#T::new()",
29+
smithyRuntime(runtimeConfig).resolve("client::connectors::connection_poisoning::ConnectionPoisoningInterceptor"),
30+
)
31+
}
32+
}
33+
34+
else -> emptySection
35+
}
36+
}
37+
}

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ private class HttpConnectorConfigCustomization(
4040
*preludeScope,
4141
"Connection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::Connection"),
4242
"ConnectorSettings" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::ConnectorSettings"),
43-
"DynConnection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::DynConnection"),
44-
"DynConnectorAdapter" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::connections::adapter::DynConnectorAdapter"),
43+
"DynConnector" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::connectors::DynConnector"),
44+
"DynConnectorAdapter" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::connectors::adapter::DynConnectorAdapter"),
4545
"HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"),
4646
"SharedAsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::SharedAsyncSleep"),
4747
"TimeoutConfig" to RuntimeType.smithyTypes(runtimeConfig).resolve("timeout::TimeoutConfig"),
@@ -197,14 +197,14 @@ private class HttpConnectorConfigCustomization(
197197
198198
let connector_settings = #{ConnectorSettings}::from_timeout_config(&timeout_config);
199199
200-
if let Some(connection) = layer.load::<#{HttpConnector}>()
200+
if let Some(connector) = layer.load::<#{HttpConnector}>()
201201
.and_then(|c| c.connector(&connector_settings, sleep_impl.clone()))
202202
.or_else(|| #{default_connector}(&connector_settings, sleep_impl)) {
203-
let connection: #{DynConnection} = #{DynConnection}::new(#{DynConnectorAdapter}::new(
203+
let connector: #{DynConnector} = #{DynConnector}::new(#{DynConnectorAdapter}::new(
204204
// TODO(enableNewSmithyRuntimeCleanup): Replace the tower-based DynConnector and remove DynConnectorAdapter when deleting the middleware implementation
205-
connection
205+
connector
206206
));
207-
#{ConfigBagAccessors}::set_connection(&mut layer, connection);
207+
#{ConfigBagAccessors}::set_connector(&mut layer, connector);
208208
}
209209
210210
""",

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
1515
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
1616
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
1717
import software.amazon.smithy.rust.codegen.core.rustlang.writable
18-
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
1918
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
2019
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
2120
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
@@ -26,8 +25,7 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon
2625
private val retryConfig = RuntimeType.smithyTypes(runtimeConfig).resolve("retry")
2726
private val sleepModule = RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep")
2827
private val timeoutModule = RuntimeType.smithyTypes(runtimeConfig).resolve("timeout")
29-
private val smithyRuntimeCrate = RuntimeType.smithyRuntime(runtimeConfig)
30-
private val retries = smithyRuntimeCrate.resolve("client::retries")
28+
private val retries = RuntimeType.smithyRuntime(runtimeConfig).resolve("client::retries")
3129
private val moduleUseName = codegenContext.moduleUseName()
3230
private val codegenScope = arrayOf(
3331
*preludeScope,
@@ -367,7 +365,7 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon
367365
}
368366
}
369367

370-
ServiceConfig.BuilderBuild -> {
368+
is ServiceConfig.BuilderBuild -> {
371369
if (runtimeMode.defaultToOrchestrator) {
372370
rustTemplate(
373371
"""
@@ -420,7 +418,10 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon
420418
}
421419
}
422420

423-
class ResiliencyReExportCustomization(private val runtimeConfig: RuntimeConfig) {
421+
class ResiliencyReExportCustomization(codegenContext: ClientCodegenContext) {
422+
private val runtimeConfig = codegenContext.runtimeConfig
423+
private val runtimeMode = codegenContext.smithyRuntimeMode
424+
424425
fun extras(rustCrate: RustCrate) {
425426
rustCrate.withModule(ClientRustModule.config) {
426427
rustTemplate(
@@ -430,13 +431,16 @@ class ResiliencyReExportCustomization(private val runtimeConfig: RuntimeConfig)
430431
}
431432
rustCrate.withModule(ClientRustModule.Config.retry) {
432433
rustTemplate(
433-
"pub use #{types_retry}::{RetryConfig, RetryConfigBuilder, RetryMode};",
434+
"pub use #{types_retry}::{RetryConfig, RetryConfigBuilder, RetryMode, ReconnectMode};",
434435
"types_retry" to RuntimeType.smithyTypes(runtimeConfig).resolve("retry"),
435436
)
436-
rustTemplate(
437-
"pub use #{RetryPartition};",
438-
"RetryPartition" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::retries::RetryPartition"),
439-
)
437+
438+
if (runtimeMode.generateOrchestrator) {
439+
rustTemplate(
440+
"pub use #{types_retry}::RetryPartition;",
441+
"types_retry" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::retries"),
442+
)
443+
}
440444
}
441445
rustCrate.withModule(ClientRustModule.Config.timeout) {
442446
rustTemplate(
@@ -449,30 +453,34 @@ class ResiliencyReExportCustomization(private val runtimeConfig: RuntimeConfig)
449453

450454
class ResiliencyServiceRuntimePluginCustomization(codegenContext: ClientCodegenContext) : ServiceRuntimePluginCustomization() {
451455
private val runtimeConfig = codegenContext.runtimeConfig
452-
private val smithyRuntimeCrate = RuntimeType.smithyRuntime(runtimeConfig)
453-
private val retries = smithyRuntimeCrate.resolve("client::retries")
456+
private val runtimeMode = codegenContext.smithyRuntimeMode
457+
private val smithyRuntime = RuntimeType.smithyRuntime(runtimeConfig)
458+
private val retries = smithyRuntime.resolve("client::retries")
454459
private val codegenScope = arrayOf(
455460
"TokenBucket" to retries.resolve("TokenBucket"),
456461
"TokenBucketPartition" to retries.resolve("TokenBucketPartition"),
457462
"ClientRateLimiter" to retries.resolve("ClientRateLimiter"),
458463
"ClientRateLimiterPartition" to retries.resolve("ClientRateLimiterPartition"),
459-
"StaticPartitionMap" to smithyRuntimeCrate.resolve("static_partition_map::StaticPartitionMap"),
464+
"StaticPartitionMap" to smithyRuntime.resolve("static_partition_map::StaticPartitionMap"),
460465
)
461466

462467
override fun section(section: ServiceRuntimePluginSection): Writable = writable {
463-
when (section) {
464-
is ServiceRuntimePluginSection.DeclareSingletons -> {
465-
// TODO(enableNewSmithyRuntimeCleanup) We can use the standard library's `OnceCell` once we upgrade the
466-
// MSRV to 1.70
467-
rustTemplate(
468-
"""
469-
static TOKEN_BUCKET: #{StaticPartitionMap}<#{TokenBucketPartition}, #{TokenBucket}> = #{StaticPartitionMap}::new();
470-
static CLIENT_RATE_LIMITER: #{StaticPartitionMap}<#{ClientRateLimiterPartition}, #{ClientRateLimiter}> = #{StaticPartitionMap}::new();
471-
""",
472-
*codegenScope,
473-
)
468+
if (runtimeMode.generateOrchestrator) {
469+
when (section) {
470+
is ServiceRuntimePluginSection.DeclareSingletons -> {
471+
// TODO(enableNewSmithyRuntimeCleanup) We can use the standard library's `OnceCell` once we upgrade the
472+
// MSRV to 1.70
473+
rustTemplate(
474+
"""
475+
static TOKEN_BUCKET: #{StaticPartitionMap}<#{TokenBucketPartition}, #{TokenBucket}> = #{StaticPartitionMap}::new();
476+
static CLIENT_RATE_LIMITER: #{StaticPartitionMap}<#{ClientRateLimiterPartition}, #{ClientRateLimiter}> = #{StaticPartitionMap}::new();
477+
""",
478+
*codegenScope,
479+
)
480+
}
481+
482+
else -> emptySection
474483
}
475-
else -> emptySection
476484
}
477485
}
478486
}

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.customize
88
import software.amazon.smithy.model.shapes.OperationShape
99
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
1010
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
11+
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ConnectionPoisoningRuntimePluginCustomization
1112
import software.amazon.smithy.rust.codegen.client.smithy.customizations.EndpointPrefixGenerator
1213
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpChecksumRequiredGenerator
1314
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpVersionListCustomization
@@ -82,7 +83,7 @@ class RequiredCustomizations : ClientCodegenDecorator {
8283
rustCrate.mergeFeature(TestUtilFeature)
8384

8485
// Re-export resiliency types
85-
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(rustCrate)
86+
ResiliencyReExportCustomization(codegenContext).extras(rustCrate)
8687

8788
rustCrate.withModule(ClientRustModule.Primitives) {
8889
pubUseSmithyPrimitives(codegenContext, codegenContext.model)(this)
@@ -102,7 +103,9 @@ class RequiredCustomizations : ClientCodegenDecorator {
102103
codegenContext: ClientCodegenContext,
103104
baseCustomizations: List<ServiceRuntimePluginCustomization>,
104105
): List<ServiceRuntimePluginCustomization> = if (codegenContext.smithyRuntimeMode.generateOrchestrator) {
105-
baseCustomizations + ResiliencyServiceRuntimePluginCustomization(codegenContext)
106+
baseCustomizations +
107+
ResiliencyServiceRuntimePluginCustomization(codegenContext) +
108+
ConnectionPoisoningRuntimePluginCustomization(codegenContext)
106109
} else {
107110
baseCustomizations
108111
}

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,12 @@ sealed class ServiceRuntimePluginSection(name: String) : Section(name) {
6767
data class RegisterInterceptor(val interceptorRegistrarName: String) : ServiceRuntimePluginSection("RegisterInterceptor") {
6868
/** Generates the code to register an interceptor */
6969
fun registerInterceptor(runtimeConfig: RuntimeConfig, writer: RustWriter, interceptor: Writable) {
70-
val smithyRuntimeApi = RuntimeType.smithyRuntimeApi(runtimeConfig)
7170
writer.rustTemplate(
7271
"""
7372
$interceptorRegistrarName.register(#{SharedInterceptor}::new(#{interceptor}) as _);
7473
""",
7574
"interceptor" to interceptor,
76-
"SharedInterceptor" to smithyRuntimeApi.resolve("client::interceptors::SharedInterceptor"),
75+
"SharedInterceptor" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::interceptors::SharedInterceptor"),
7776
)
7877
}
7978
}

codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomizationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ internal class ResiliencyConfigCustomizationTest {
4040
val codegenContext = testClientCodegenContext(model, settings = project.clientRustSettings())
4141

4242
stubConfigProject(codegenContext, ResiliencyConfigCustomization(codegenContext), project)
43-
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(project)
43+
ResiliencyReExportCustomization(codegenContext).extras(project)
4444
project.compileAndTest()
4545
}
4646
}

0 commit comments

Comments
 (0)