Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions aws/rust-runtime/aws-config/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 81 additions & 11 deletions aws/sdk/integration-tests/s3/tests/timeouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,25 +166,95 @@ async fn test_read_timeout() {
server_handle.await.unwrap();
}

#[tokio::test]
async fn test_connect_timeout() {
let config = Config::builder()
async fn run_connect_timeout_test(timeout_config: Option<TimeoutConfig>, expected_timeout_ms: u64) {
let mut config_builder = Config::builder()
.with_test_defaults()
.region(Region::new("us-east-1"))
.timeout_config(
.endpoint_url("http://172.255.255.0:18104");

if let Some(tc) = timeout_config {
config_builder = config_builder.timeout_config(tc);
}

let client = Client::from_conf(config_builder.build());

if let Ok(result) = timeout(
Duration::from_millis(expected_timeout_ms + 1000),
client.get_object().bucket("test").key("test").send(),
)
.await
{
match result {
Ok(_) => panic!("should not have succeeded"),
Err(err) => {
let message = format!("{}", DisplayErrorContext(&err));
let expected =
"timeout: client error (Connect): HTTP connect timeout occurred after";
assert!(
message.contains(expected),
"expected '{message}' to contain '{expected}'"
);
}
}
} else {
panic!("the client didn't timeout");
}
}

#[tokio::test]
async fn test_connect_timeout_explicit() {
run_connect_timeout_test(
Some(
TimeoutConfig::builder()
.connect_timeout(Duration::from_millis(300))
.build(),
)
.endpoint_url(
// Emulate a connect timeout error by hitting an unroutable IP
"http://172.255.255.0:18104",
)
),
300,
)
.await;
}

#[tokio::test]
async fn test_connect_timeout_default() {
run_connect_timeout_test(None, 3100).await;
}

#[tokio::test]
async fn test_connect_timeout_disabled() {
let config = Config::builder()
.with_test_defaults()
.region(Region::new("us-east-1"))
.timeout_config(TimeoutConfig::disabled())
.endpoint_url("http://172.255.255.0:18104")
.build();
let client = Client::from_conf(config);

if let Err(_) = timeout(
Duration::from_secs(5),
client.get_object().bucket("test").key("test").send(),
)
.await
{
// Expected: the operation should not complete within 5 seconds when timeout is disabled
} else {
panic!("operation completed unexpectedly when timeout was disabled");
}
}

// this behavior is surprising—I would have expected this to fail, but it seems like the default
// runtime plugin always is providing a sleep impl. In any case, this pattern does not seem to exist
// in real life
#[tokio::test]
async fn test_connect_timeout_with_sleep_impl_none() {
let mut builder = Config::builder()
.with_test_defaults()
.region(Region::new("us-east-1"))
.endpoint_url("http://172.255.255.0:18104");
builder.set_sleep_impl(None);
let client = Client::from_conf(builder.build());

if let Ok(result) = timeout(
Duration::from_millis(1000),
Duration::from_millis(4100),
client.get_object().bucket("test").key("test").send(),
)
.await
Expand All @@ -194,7 +264,7 @@ async fn test_connect_timeout() {
Err(err) => {
let message = format!("{}", DisplayErrorContext(&err));
let expected =
"timeout: client error (Connect): HTTP connect timeout occurred after 300ms";
"timeout: client error (Connect): HTTP connect timeout occurred after";
assert!(
message.contains(expected),
"expected '{message}' to contain '{expected}'"
Expand Down
4 changes: 2 additions & 2 deletions codegen-server-test/integration-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion rust-runtime/aws-smithy-runtime/src/client/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,30 @@ pub fn default_timeout_config_plugin() -> Option<SharedRuntimePlugin> {
)
}

/// Runtime plugin that sets the default timeout config (no timeouts).
pub fn default_timeout_config_plugin_v2() -> Option<SharedRuntimePlugin> {
Some(
default_plugin("default_timeout_config_plugin", |components| {
components.with_config_validator(SharedConfigValidator::base_client_config_fn(
validate_timeout_config,
))
})
.with_config(layer("default_timeout_config", |layer| {
let timeout_config = if default_sleep_impl_plugin().is_some() {
TimeoutConfig::builder()
.connect_timeout(Duration::from_millis(3100))
.disable_operation_attempt_timeout()
.disable_operation_timeout()
.build()
} else {
TimeoutConfig::disabled()
};
layer.store_put(timeout_config);
}))
.into_shared(),
)
}

fn validate_timeout_config(
components: &RuntimeComponentsBuilder,
cfg: &ConfigBag,
Expand Down Expand Up @@ -327,7 +351,7 @@ pub fn default_plugins(
),
default_sleep_impl_plugin(),
default_time_source_plugin(),
default_timeout_config_plugin(),
default_timeout_config_plugin_v2(),
enforce_content_length_runtime_plugin(),
default_stalled_stream_protection_config_plugin_v2(behavior_version),
]
Expand Down
Loading