Skip to content

Commit dd10f06

Browse files
fix hyper 1.x connection refused errors not marked as retryable (#4190)
## Description internal ref: `V1820425457` Fixes a regression in the way connection issues are classified which was causing them to not be retried when using the hyper 1.x client. The issue is the logic for [classifying the error](https://github.com/smithy-lang/smithy-rs/blob/release-2025-06-11/rust-runtime/aws-smithy-http-client/src/client.rs#L401) was never being hit because connection errors are no longer `hyper::Error` they are `hyper_util::client::legacy::Error`. * [hyper-util::Error](https://docs.rs/hyper-util/0.1.14/hyper_util/client/legacy/struct.Error.html) * [hyper@1.x::Error](https://docs.rs/hyper/1.6.0/hyper/struct.Error.html) * [hyper@0.14.x::Error](https://docs.rs/hyper/0.14.32/hyper/struct.Error.html#method.is_connect) ## Testing New unit test plus verification against internal package that opened the issue. ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [x] For changes to the smithy-rs codegen or runtime crates, I have created a changelog entry Markdown file in the `.changelog` directory, specifying "client," "server," or both in the `applies_to` key. ---- _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: Landon James <lnj@amazon.com>
1 parent 15d0d3d commit dd10f06

File tree

6 files changed

+78
-9
lines changed

6 files changed

+78
-9
lines changed

.changelog/1750957379.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
applies_to:
3+
- client
4+
- aws-sdk-rust
5+
authors:
6+
- aajtodd
7+
references: []
8+
breaking: false
9+
new_feature: false
10+
bug_fix: true
11+
---
12+
Fix hyper 1.x connection refused errors not marked as retryable

aws/rust-runtime/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aws/rust-runtime/aws-config/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-runtime/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-runtime/aws-smithy-http-client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "aws-smithy-http-client"
33
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
44
description = "HTTP client abstractions for generated smithy clients"
5-
version = "1.0.5"
5+
version = "1.0.6"
66
license = "Apache-2.0"
77
edition = "2021"
88
repository = "https://github.com/smithy-lang/smithy-rs"

rust-runtime/aws-smithy-http-client/src/client.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
mod dns;
67
mod timeout;
78
/// TLS connector(s)
89
pub mod tls;
@@ -400,7 +401,17 @@ fn downcast_error(err: BoxError) -> ConnectorError {
400401
// error classifications
401402
let err = match find_source::<hyper::Error>(err.as_ref()) {
402403
Some(hyper_error) => return to_connector_error(hyper_error)(err),
403-
None => err,
404+
None => match find_source::<hyper_util::client::legacy::Error>(err.as_ref()) {
405+
Some(hyper_util_err) => {
406+
if hyper_util_err.is_connect()
407+
|| find_source::<std::io::Error>(hyper_util_err).is_some()
408+
{
409+
return ConnectorError::io(err);
410+
}
411+
err
412+
}
413+
None => err,
414+
},
404415
};
405416

406417
// otherwise, we have no idea!
@@ -559,7 +570,6 @@ pub struct Builder<Tls = TlsUnset> {
559570
}
560571

561572
cfg_tls! {
562-
mod dns;
563573
use aws_smithy_runtime_api::client::dns::ResolveDns;
564574

565575
impl ConnectorBuilder<TlsProviderSelected> {
@@ -757,6 +767,7 @@ mod test {
757767
use std::sync::Arc;
758768
use std::task::{Context, Poll};
759769

770+
use crate::client::timeout::test::NeverConnects;
760771
use aws_smithy_async::assert_elapsed;
761772
use aws_smithy_async::rt::sleep::TokioSleep;
762773
use aws_smithy_async::time::SystemTimeSource;
@@ -765,8 +776,6 @@ mod test {
765776
use hyper::rt::ReadBufCursor;
766777
use hyper_util::client::legacy::connect::Connected;
767778

768-
use crate::client::timeout::test::NeverConnects;
769-
770779
use super::*;
771780

772781
#[tokio::test]
@@ -963,6 +972,54 @@ mod test {
963972
assert_elapsed!(now, Duration::from_secs(2));
964973
}
965974

975+
#[cfg(not(windows))]
976+
#[tokio::test]
977+
async fn connection_refused_works() {
978+
use crate::client::dns::HyperUtilResolver;
979+
use aws_smithy_runtime_api::client::dns::{DnsFuture, ResolveDns};
980+
use std::net::{IpAddr, Ipv4Addr};
981+
982+
#[derive(Debug, Clone, Default)]
983+
struct TestResolver;
984+
impl ResolveDns for TestResolver {
985+
fn resolve_dns<'a>(&'a self, _name: &'a str) -> DnsFuture<'a> {
986+
let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
987+
DnsFuture::ready(Ok(vec![localhost_v4]))
988+
}
989+
}
990+
991+
let connector_settings = HttpConnectorSettings::builder()
992+
.connect_timeout(Duration::from_secs(20))
993+
.build();
994+
995+
let resolver = HyperUtilResolver {
996+
resolver: TestResolver::default(),
997+
};
998+
let connector = Connector::builder().base_connector_with_resolver(resolver);
999+
1000+
let hyper = Connector::builder()
1001+
.connector_settings(connector_settings)
1002+
.sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
1003+
.wrap_connector(connector)
1004+
.adapter;
1005+
1006+
let resp = hyper
1007+
.call(HttpRequest::get("http://static-uri:50227.com").unwrap())
1008+
.await
1009+
.unwrap_err();
1010+
assert!(
1011+
resp.is_io(),
1012+
"expected resp.is_io() to be true but it was false, resp == {:?}",
1013+
resp
1014+
);
1015+
let message = DisplayErrorContext(&resp).to_string();
1016+
let expected = "Connection refused";
1017+
assert!(
1018+
message.contains(expected),
1019+
"expected '{message}' to contain '{expected}'"
1020+
);
1021+
}
1022+
9661023
#[cfg(feature = "s2n-tls")]
9671024
#[tokio::test]
9681025
async fn s2n_tls_provider() {

0 commit comments

Comments
 (0)