Skip to content

Commit dc3c876

Browse files
authored
Refactor SDK's request retry, and error handling behavior (#487)
This update refactors the SDK's request retry behavior by updating the SDK's implementation to a model that will be the standard across the SDKs. This model simplifies retry behavior, and will retry all connection errors. Not just write connection reset errors. This update also refactors the SDK's error wrapping and handling. The SDK's error types such as awserr.Error implement the Unwrap method allowing the nested errors to be more easily extracted using the Go 1.13 errors.As, Is, and Unwrap functions. This update also provides more information about why a request failed, and was retried, but exhausted attempts. The aws.MaxAttemptsError will wrap the connection or service API error.
1 parent 9fc62ee commit dc3c876

File tree

327 files changed

+4342
-2198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

327 files changed

+4342
-2198
lines changed

CHANGELOG_PENDING.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Breaking Change
22
---
33

4+
* Update SDK retry behavior
5+
* Significant updates were made the SDK's retry behavior. The SDK will now retry all connections error. In addition, to changing what errors are retried the SDK's retry behavior not distinguish the difference between throttling errors, and regular retryable errors. All errors will be retried with the same backoff jitter delay scaling.
6+
* The SDK will attempt an operation request 3 times by default. This is one less than the previous initial request with 3 retires.
7+
* New helper functions in the new `aws/retry` package allow wrapping a `Retrier` with custom behavior, overriding the base `Retrier`, (e.g. `AddWithErrorCodes`, and `AddWithMaxAttempts`)
8+
* Update SDK error handling
9+
* Updates the SDK's handling of errors to take advantage of Go 1.13's new `errors.As`, `Is`, and `Unwrap`. The SDK's errors were updated to satisfy the `Unwrap` interface, returning the underlying error.
10+
* With this update, you can now more easily access the SDK's layered errors, and meaningful state such as, `Timeout`, `Temporary`, and other states added to the SDK such as `CanceledError`.
11+
* Bump SDK minimum supported version from Go 1.12 to Go 1.13
12+
* The SDK's minimum supported version is bumped to take advantage of Go 1.13's updated `errors` package.
13+
414
Services
515
---
616

@@ -14,6 +24,10 @@ SDK Features
1424
* Fixes [#338](https://github.com/aws/aws-sdk-go-v2/issues/338)
1525
* Adds Support for `credential_source`
1626
* Fixes [#274](https://github.com/aws/aws-sdk-go-v2/issues/274)
27+
* `aws/awserr`: Adds support for Go 1.13's `errors.Unwrap` ([#487](https://github.com/aws/aws-sdk-go-v2/pull/487))
28+
* `aws`: Updates SDK retry behavior ([#487](https://github.com/aws/aws-sdk-go-v2/pull/487))
29+
* `aws/retry`: New package defining logic to determine if a request should be retried, and backoff.
30+
* `aws/ratelimit`: New package defining rate limit logic such as token bucket used by the `retry.Standard` retrier.
1731

1832
SDK Enhancements
1933
---

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
`aws-sdk-go-v2` is the **Developer Preview** (aka **beta**) for the v2 AWS SDK for the Go programming language. This Developer Preview is provided to receive feedback from the language community on SDK changes prior to the final release. As such users should expect the SDK to release minor version releases that break backwards compatability. The release notes for the breaking change will include information about the breaking change, and how you can migrate to the latest version.
66

7-
Check out the [Issues] and [Projects] for design and updates being made to the SDK. The v2 SDK requires a minimum version of `Go 1.12`.
7+
Check out the [Issues] and [Projects] for design and updates being made to the SDK. The v2 SDK requires a minimum version of `Go 1.13`.
88

99
We'll be expanding out the [Issues] and [Projects] sections with additional changes to the SDK based on your feedback, and SDK's core's improvements. Check the the SDK's [CHANGE_LOG] for information about the latest updates to the SDK.
1010

aws/awserr/error.go

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ package awserr
1616
// // Get error details
1717
// log.Println("Error:", awsErr.Code(), awsErr.Message())
1818
//
19-
// // Prints out full error message, including original error if there was one.
20-
// log.Println("Error:", awsErr.Error())
19+
// // Prints out full error message, including original error if
20+
// // there was one.
21+
// log.Println("Error:", awsErr)
2122
//
2223
// // Get original error
23-
// if origErr := awsErr.OrigErr(); origErr != nil {
24+
// if origErr := errors.Unwrap(awsErr); origErr != nil {
2425
// // operate on original error.
2526
// }
2627
// } else {
27-
// fmt.Println(err.Error())
28+
// fmt.Println(err)
2829
// }
2930
// }
3031
//
@@ -37,60 +38,31 @@ type Error interface {
3738

3839
// Returns the error details message.
3940
Message() string
40-
41-
// Returns the original error if one was set. Nil is returned if not set.
42-
OrigErr() error
43-
}
44-
45-
// BatchError is a batch of errors which also wraps lower level errors with
46-
// code, message, and original errors. Calling Error() will include all errors
47-
// that occurred in the batch.
48-
//
49-
// Deprecated: Replaced with BatchedErrors. Only defined for backwards
50-
// compatibility.
51-
type BatchError interface {
52-
// Satisfy the generic error interface.
53-
error
54-
55-
// Returns the short phrase depicting the classification of the error.
56-
Code() string
57-
58-
// Returns the error details message.
59-
Message() string
60-
61-
// Returns the original error if one was set. Nil is returned if not set.
62-
OrigErrs() []error
6341
}
6442

6543
// BatchedErrors is a batch of errors which also wraps lower level errors with
6644
// code, message, and original errors. Calling Error() will include all errors
6745
// that occurred in the batch.
68-
//
69-
// Replaces BatchError
7046
type BatchedErrors interface {
7147
// Satisfy the base Error interface.
7248
Error
7349

7450
// Returns the original error if one was set. Nil is returned if not set.
75-
OrigErrs() []error
51+
Errs() []error
7652
}
7753

7854
// New returns an Error object described by the code, message, and origErr.
7955
//
8056
// If origErr satisfies the Error interface it will not be wrapped within a new
8157
// Error object and will instead be returned.
82-
func New(code, message string, origErr error) Error {
83-
var errs []error
84-
if origErr != nil {
85-
errs = append(errs, origErr)
86-
}
87-
return newBaseError(code, message, errs)
58+
func New(code, message string, err error) Error {
59+
return newBaseError(code, message, err)
8860
}
8961

9062
// NewBatchError returns an BatchedErrors with a collection of errors as an
9163
// array of errors.
9264
func NewBatchError(code, message string, errs []error) BatchedErrors {
93-
return newBaseError(code, message, errs)
65+
return newBatchError(code, message, errs)
9466
}
9567

9668
// A RequestFailure is an interface to extract request failure information from

aws/awserr/types.go

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package awserr
22

3-
import "fmt"
3+
import (
4+
"errors"
5+
"fmt"
6+
)
47

58
// SprintError returns a string of the formatted error code.
69
//
@@ -12,7 +15,7 @@ func SprintError(code, message, extra string, origErr error) string {
1215
msg = fmt.Sprintf("%s\n\t%s", msg, extra)
1316
}
1417
if origErr != nil {
15-
msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
18+
msg = fmt.Sprintf("%s\ncaused by: %v", msg, origErr)
1619
}
1720
return msg
1821
}
@@ -31,7 +34,7 @@ type baseError struct {
3134

3235
// Optional original error this error is based off of. Allows building
3336
// chained errors.
34-
errs []error
37+
err error
3538
}
3639

3740
// newBaseError returns an error object for the code, message, and errors.
@@ -44,11 +47,11 @@ type baseError struct {
4447
//
4548
// origErrs is the error objects which will be nested under the new errors to
4649
// be returned.
47-
func newBaseError(code, message string, origErrs []error) *baseError {
50+
func newBaseError(code, message string, err error) *baseError {
4851
b := &baseError{
4952
code: code,
5053
message: message,
51-
errs: origErrs,
54+
err: err,
5255
}
5356

5457
return b
@@ -60,12 +63,7 @@ func newBaseError(code, message string, origErrs []error) *baseError {
6063
//
6164
// Satisfies the error interface.
6265
func (b baseError) Error() string {
63-
size := len(b.errs)
64-
if size > 0 {
65-
return SprintError(b.code, b.message, "", errorList(b.errs))
66-
}
67-
68-
return SprintError(b.code, b.message, "", nil)
66+
return SprintError(b.code, b.message, "", b.err)
6967
}
7068

7169
// String returns the string representation of the error.
@@ -84,27 +82,37 @@ func (b baseError) Message() string {
8482
return b.message
8583
}
8684

87-
// OrigErr returns the original error if one was set. Nil is returned if no
85+
// Unwrap returns the original error if one was set. Nil is returned if no
8886
// error was set. This only returns the first element in the list. If the full
8987
// list is needed, use BatchedErrors.
90-
func (b baseError) OrigErr() error {
91-
switch len(b.errs) {
92-
case 0:
93-
return nil
94-
case 1:
95-
return b.errs[0]
96-
default:
97-
if err, ok := b.errs[0].(Error); ok {
98-
return NewBatchError(err.Code(), err.Message(), b.errs[1:])
99-
}
100-
return NewBatchError("BatchedErrors",
101-
"multiple errors occurred", b.errs)
88+
func (b baseError) Unwrap() error {
89+
return b.err
90+
}
91+
92+
type batchError struct {
93+
*baseError
94+
errs []error
95+
}
96+
97+
func newBatchError(code, message string, errs []error) *batchError {
98+
return &batchError{
99+
baseError: newBaseError(code, message, nil),
100+
errs: errs,
101+
}
102+
}
103+
104+
func (b batchError) Error() string {
105+
size := len(b.errs)
106+
if size > 0 {
107+
return SprintError(b.code, b.message, "", errorList(b.errs))
102108
}
109+
110+
return SprintError(b.code, b.message, "", nil)
103111
}
104112

105-
// OrigErrs returns the original errors if one was set. An empty slice is
106-
// returned if no error was set.
107-
func (b baseError) OrigErrs() []error {
113+
// Errs returns the original errors if one was set. Nil is returned if no error
114+
// was set.
115+
func (b batchError) Errs() []error {
108116
return b.errs
109117
}
110118

@@ -142,13 +150,11 @@ func newRequestError(err Error, statusCode int, requestID string) *requestError
142150
func (r requestError) Error() string {
143151
extra := fmt.Sprintf("status code: %d, request id: %s",
144152
r.statusCode, r.requestID)
145-
return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
153+
return SprintError(r.Code(), r.Message(), extra, r.Unwrap())
146154
}
147155

148-
// String returns the string representation of the error.
149-
// Alias for Error to satisfy the stringer interface.
150-
func (r requestError) String() string {
151-
return r.Error()
156+
func (r requestError) Unwrap() error {
157+
return errors.Unwrap(r.awsError)
152158
}
153159

154160
// StatusCode returns the wrapped status code for the error
@@ -161,13 +167,14 @@ func (r requestError) RequestID() string {
161167
return r.requestID
162168
}
163169

164-
// OrigErrs returns the original errors if one was set. An empty slice is
165-
// returned if no error was set.
166-
func (r requestError) OrigErrs() []error {
170+
// Errs returns the original errors if one was set. Nil is returned if no error
171+
// is set.
172+
func (r requestError) Errs() []error {
167173
if b, ok := r.awsError.(BatchedErrors); ok {
168-
return b.OrigErrs()
174+
return b.Errs()
169175
}
170-
return []error{r.OrigErr()}
176+
177+
return nil
171178
}
172179

173180
// An error list that satisfies the golang interface

aws/client.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ func NewClient(cfg Config, metadata Metadata) *Client {
5353
svc.Config.HTTPClient = wrapWithoutRedirect(c)
5454
}
5555

56-
retryer := cfg.Retryer
57-
if retryer == nil {
58-
retryer = NewDefaultRetryer()
59-
}
60-
svc.Retryer = retryer
6156
svc.AddDebugHandlers()
6257
return svc
6358
}

aws/config.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,8 @@ type Config struct {
3232
Handlers Handlers
3333

3434
// Retryer guides how HTTP requests should be retried in case of
35-
// recoverable failures.
36-
//
37-
// When nil or the value does not implement the request.Retryer interface,
38-
// the client.DefaultRetryer will be used.
39-
//
40-
// When both Retryer and MaxRetries are non-nil, the former is used and
41-
// the latter ignored.
42-
//
43-
// To set the Retryer field in a type-safe manner and with chaining, use
44-
// the request.WithRetryer helper function:
45-
//
46-
// cfg := request.WithRetryer(aws.NewConfig(), myRetryer)
35+
// recoverable failures. When nil the API client will use a default
36+
// retryer.
4737
Retryer Retryer
4838

4939
// An integer value representing the logging level. The default log level

aws/connection_reset_error.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

aws/connection_reset_error_other.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

aws/connection_reset_error_other_test.go

Lines changed: 0 additions & 9 deletions
This file was deleted.

aws/connection_reset_error_test.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)