diff --git a/.changelog/63caeb72930c4d84bbab8d36fe10d4b5.json b/.changelog/63caeb72930c4d84bbab8d36fe10d4b5.json new file mode 100644 index 00000000000..eed333dc0e3 --- /dev/null +++ b/.changelog/63caeb72930c4d84bbab8d36fe10d4b5.json @@ -0,0 +1,8 @@ +{ + "id": "63caeb72-930c-4d84-bbab-8d36fe10d4b5", + "type": "bugfix", + "description": "Improve recognition of retryable DNS errors.", + "modules": [ + "." + ] +} \ No newline at end of file diff --git a/aws/retry/retryable_error.go b/aws/retry/retryable_error.go index 00d7d3eeeaf..987affdde6f 100644 --- a/aws/retry/retryable_error.go +++ b/aws/retry/retryable_error.go @@ -97,11 +97,21 @@ func (r RetryableConnectionError) IsErrorRetryable(err error) aws.Ternary { var netOpErr *net.OpError var dnsError *net.DNSError - switch { - case errors.As(err, &dnsError): + if errors.As(err, &dnsError) { // NXDOMAIN errors should not be retried - retryable = !dnsError.IsNotFound && dnsError.IsTemporary + if dnsError.IsNotFound { + return aws.BoolTernary(false) + } + + // if !dnsError.Temporary(), error may or may not be temporary, + // (i.e. !Temporary() =/=> !retryable) so we should fall through to + // remaining checks + if dnsError.Temporary() { + return aws.BoolTernary(true) + } + } + switch { case errors.As(err, &conErr) && conErr.ConnectionError(): retryable = true diff --git a/aws/retry/retryable_error_test.go b/aws/retry/retryable_error_test.go index b4a425c7e11..470d1b1e76c 100644 --- a/aws/retry/retryable_error_test.go +++ b/aws/retry/retryable_error_test.go @@ -262,3 +262,47 @@ func TestCanceledError(t *testing.T) { }) } } + +func TestDNSError(t *testing.T) { + cases := map[string]struct { + Err error + Expect aws.Ternary + }{ + "IsNotFound": { + Err: &net.DNSError{ + IsNotFound: true, + }, + Expect: aws.FalseTernary, + }, + "Temporary (IsTimeout)": { + Err: &net.DNSError{ + IsTimeout: true, + }, + Expect: aws.TrueTernary, + }, + "Temporary (IsTemporary)": { + Err: &net.DNSError{ + IsTemporary: true, + }, + Expect: aws.TrueTernary, + }, + "Temporary() == false but it falls through": { + Err: &net.OpError{ + Op: "dial", + Err: &net.DNSError{}, + }, + Expect: aws.TrueTernary, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + var r RetryableConnectionError + + retryable := r.IsErrorRetryable(c.Err) + if e, a := c.Expect, retryable; e != a { + t.Errorf("expect %v retryable, got %v", e, a) + } + }) + } +}