1package client
2
3import (
4	"strconv"
5	"time"
6
7	"github.com/aws/aws-sdk-go/aws/request"
8	"github.com/aws/aws-sdk-go/internal/sdkrand"
9)
10
11// DefaultRetryer implements basic retry logic using exponential backoff for
12// most services. If you want to implement custom retry logic, implement the
13// request.Retryer interface or create a structure type that composes this
14// struct and override the specific methods. For example, to override only
15// the MaxRetries method:
16//
17//		type retryer struct {
18//      client.DefaultRetryer
19//    }
20//
21//    // This implementation always has 100 max retries
22//    func (d retryer) MaxRetries() int { return 100 }
23type DefaultRetryer struct {
24	NumMaxRetries int
25}
26
27// MaxRetries returns the number of maximum returns the service will use to make
28// an individual API request.
29func (d DefaultRetryer) MaxRetries() int {
30	return d.NumMaxRetries
31}
32
33// RetryRules returns the delay duration before retrying this request again
34func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
35	// Set the upper limit of delay in retrying at ~five minutes
36	minTime := 30
37	throttle := d.shouldThrottle(r)
38	if throttle {
39		if delay, ok := getRetryDelay(r); ok {
40			return delay
41		}
42
43		minTime = 500
44	}
45
46	retryCount := r.RetryCount
47	if throttle && retryCount > 8 {
48		retryCount = 8
49	} else if retryCount > 13 {
50		retryCount = 13
51	}
52
53	delay := (1 << uint(retryCount)) * (sdkrand.SeededRand.Intn(minTime) + minTime)
54	return time.Duration(delay) * time.Millisecond
55}
56
57// ShouldRetry returns true if the request should be retried.
58func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
59	// If one of the other handlers already set the retry state
60	// we don't want to override it based on the service's state
61	if r.Retryable != nil {
62		return *r.Retryable
63	}
64
65	if r.HTTPResponse.StatusCode >= 500 && r.HTTPResponse.StatusCode != 501 {
66		return true
67	}
68	return r.IsErrorRetryable() || d.shouldThrottle(r)
69}
70
71// ShouldThrottle returns true if the request should be throttled.
72func (d DefaultRetryer) shouldThrottle(r *request.Request) bool {
73	switch r.HTTPResponse.StatusCode {
74	case 429:
75	case 502:
76	case 503:
77	case 504:
78	default:
79		return r.IsErrorThrottle()
80	}
81
82	return true
83}
84
85// This will look in the Retry-After header, RFC 7231, for how long
86// it will wait before attempting another request
87func getRetryDelay(r *request.Request) (time.Duration, bool) {
88	if !canUseRetryAfterHeader(r) {
89		return 0, false
90	}
91
92	delayStr := r.HTTPResponse.Header.Get("Retry-After")
93	if len(delayStr) == 0 {
94		return 0, false
95	}
96
97	delay, err := strconv.Atoi(delayStr)
98	if err != nil {
99		return 0, false
100	}
101
102	return time.Duration(delay) * time.Second, true
103}
104
105// Will look at the status code to see if the retry header pertains to
106// the status code.
107func canUseRetryAfterHeader(r *request.Request) bool {
108	switch r.HTTPResponse.StatusCode {
109	case 429:
110	case 503:
111	default:
112		return false
113	}
114
115	return true
116}
117