"bytes"
"fmt"
"io"
+ "net"
"net/http"
"net/url"
"reflect"
return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
}
+func fmtAttemptCount(retryCount, maxRetries int) string {
+ return fmt.Sprintf("attempt %v/%v", retryCount, maxRetries)
+}
+
// ParamsFilled returns if the request's parameters have been populated
// and the parameters are valid. False is returned if no parameters are
// provided or invalid.
// SetReaderBody will set the request's body reader.
func (r *Request) SetReaderBody(reader io.ReadSeeker) {
r.Body = reader
- r.BodyStart, _ = reader.Seek(0, sdkio.SeekCurrent) // Get the Bodies current offset.
+
+ if aws.IsReaderSeekable(reader) {
+ var err error
+ // Get the Bodies current offset so retries will start from the same
+ // initial position.
+ r.BodyStart, err = reader.Seek(0, sdkio.SeekCurrent)
+ if err != nil {
+ r.Error = awserr.New(ErrCodeSerialization,
+ "failed to determine start of request body", err)
+ return
+ }
+ }
r.ResetBody()
}
return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
}
-func debugLogReqError(r *Request, stage string, retrying bool, err error) {
+const (
+ notRetrying = "not retrying"
+)
+
+func debugLogReqError(r *Request, stage, retryStr string, err error) {
if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
return
}
- retryStr := "not retrying"
- if retrying {
- retryStr = "will retry"
- }
-
r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
}
if !r.built {
r.Handlers.Validate.Run(r)
if r.Error != nil {
- debugLogReqError(r, "Validate Request", false, r.Error)
+ debugLogReqError(r, "Validate Request", notRetrying, r.Error)
return r.Error
}
r.Handlers.Build.Run(r)
if r.Error != nil {
- debugLogReqError(r, "Build Request", false, r.Error)
+ debugLogReqError(r, "Build Request", notRetrying, r.Error)
return r.Error
}
r.built = true
func (r *Request) Sign() error {
r.Build()
if r.Error != nil {
- debugLogReqError(r, "Build Request", false, r.Error)
+ debugLogReqError(r, "Build Request", notRetrying, r.Error)
return r.Error
}
return r.Error
}
-func (r *Request) getNextRequestBody() (io.ReadCloser, error) {
+func (r *Request) getNextRequestBody() (body io.ReadCloser, err error) {
if r.safeBody != nil {
r.safeBody.Close()
}
- r.safeBody = newOffsetReader(r.Body, r.BodyStart)
+ r.safeBody, err = newOffsetReader(r.Body, r.BodyStart)
+ if err != nil {
+ return nil, awserr.New(ErrCodeSerialization,
+ "failed to get next request body reader", err)
+ }
// Go 1.8 tightened and clarified the rules code needs to use when building
// requests with the http package. Go 1.8 removed the automatic detection
// Related golang/go#18257
l, err := aws.SeekerLen(r.Body)
if err != nil {
- return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
+ return nil, awserr.New(ErrCodeSerialization,
+ "failed to compute request body size", err)
}
- var body io.ReadCloser
if l == 0 {
body = NoBody
} else if l > 0 {
r.AttemptTime = time.Now()
if err := r.Sign(); err != nil {
- debugLogReqError(r, "Sign Request", false, err)
+ debugLogReqError(r, "Sign Request", notRetrying, err)
return err
}
if err := r.sendRequest(); err == nil {
return nil
- } else if !shouldRetryCancel(r.Error) {
+ } else if !shouldRetryError(r.Error) {
return err
} else {
r.Handlers.Retry.Run(r)
return r.Error
}
- r.prepareRetry()
+ if err := r.prepareRetry(); err != nil {
+ r.Error = err
+ return err
+ }
continue
}
}
}
-func (r *Request) prepareRetry() {
+func (r *Request) prepareRetry() error {
if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
// the request's body even though the Client's Do returned.
r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
r.ResetBody()
+ if err := r.Error; err != nil {
+ return awserr.New(ErrCodeSerialization,
+ "failed to prepare body for retry", err)
+
+ }
// Closing response body to ensure that no response body is leaked
// between retry attempts.
if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
r.HTTPResponse.Body.Close()
}
+
+ return nil
}
func (r *Request) sendRequest() (sendErr error) {
r.Retryable = nil
r.Handlers.Send.Run(r)
if r.Error != nil {
- debugLogReqError(r, "Send Request", r.WillRetry(), r.Error)
+ debugLogReqError(r, "Send Request",
+ fmtAttemptCount(r.RetryCount, r.MaxRetries()),
+ r.Error)
return r.Error
}
r.Handlers.ValidateResponse.Run(r)
if r.Error != nil {
r.Handlers.UnmarshalError.Run(r)
- debugLogReqError(r, "Validate Response", r.WillRetry(), r.Error)
+ debugLogReqError(r, "Validate Response",
+ fmtAttemptCount(r.RetryCount, r.MaxRetries()),
+ r.Error)
return r.Error
}
r.Handlers.Unmarshal.Run(r)
if r.Error != nil {
- debugLogReqError(r, "Unmarshal Response", r.WillRetry(), r.Error)
+ debugLogReqError(r, "Unmarshal Response",
+ fmtAttemptCount(r.RetryCount, r.MaxRetries()),
+ r.Error)
return r.Error
}
Temporary() bool
}
-func shouldRetryCancel(err error) bool {
- switch err := err.(type) {
+func shouldRetryError(origErr error) bool {
+ switch err := origErr.(type) {
case awserr.Error:
if err.Code() == CanceledErrorCode {
return false
}
- return shouldRetryCancel(err.OrigErr())
+ return shouldRetryError(err.OrigErr())
case *url.Error:
if strings.Contains(err.Error(), "connection refused") {
// Refused connections should be retried as the service may not yet
}
// *url.Error only implements Temporary after golang 1.6 but since
// url.Error only wraps the error:
- return shouldRetryCancel(err.Err)
+ return shouldRetryError(err.Err)
case temporary:
+ if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
+ return true
+ }
// If the error is temporary, we want to allow continuation of the
// retry process
- return err.Temporary()
+ return err.Temporary() || isErrConnectionReset(origErr)
case nil:
// `awserr.Error.OrigErr()` can be nil, meaning there was an error but
- // because we don't know the cause, it is marked as retriable. See
+ // because we don't know the cause, it is marked as retryable. See
// TestRequest4xxUnretryable for an example.
return true
default: