1package client
2
3import (
4	"math"
5	"strconv"
6	"time"
7
8	"github.com/aws/aws-sdk-go/aws/request"
9	"github.com/aws/aws-sdk-go/internal/sdkrand"
10)
11
12// DefaultRetryer implements basic retry logic using exponential backoff for
13// most services. If you want to implement custom retry logic, you can implement the
14// request.Retryer interface.
15//
16type DefaultRetryer struct {
17	// Num max Retries is the number of max retries that will be performed.
18	// By default, this is zero.
19	NumMaxRetries int
20
21	// MinRetryDelay is the minimum retry delay after which retry will be performed.
22	// If not set, the value is 0ns.
23	MinRetryDelay time.Duration
24
25	// MinThrottleRetryDelay is the minimum retry delay when throttled.
26	// If not set, the value is 0ns.
27	MinThrottleDelay time.Duration
28
29	// MaxRetryDelay is the maximum retry delay before which retry must be performed.
30	// If not set, the value is 0ns.
31	MaxRetryDelay time.Duration
32
33	// MaxThrottleDelay is the maximum retry delay when throttled.
34	// If not set, the value is 0ns.
35	MaxThrottleDelay time.Duration
36}
37
38const (
39	// DefaultRetryerMaxNumRetries sets maximum number of retries
40	DefaultRetryerMaxNumRetries = 3
41
42	// DefaultRetryerMinRetryDelay sets minimum retry delay
43	DefaultRetryerMinRetryDelay = 30 * time.Millisecond
44
45	// DefaultRetryerMinThrottleDelay sets minimum delay when throttled
46	DefaultRetryerMinThrottleDelay = 500 * time.Millisecond
47
48	// DefaultRetryerMaxRetryDelay sets maximum retry delay
49	DefaultRetryerMaxRetryDelay = 300 * time.Second
50
51	// DefaultRetryerMaxThrottleDelay sets maximum delay when throttled
52	DefaultRetryerMaxThrottleDelay = 300 * time.Second
53)
54
55// MaxRetries returns the number of maximum returns the service will use to make
56// an individual API request.
57func (d DefaultRetryer) MaxRetries() int {
58	return d.NumMaxRetries
59}
60
61// setRetryerDefaults sets the default values of the retryer if not set
62func (d *DefaultRetryer) setRetryerDefaults() {
63	if d.MinRetryDelay == 0 {
64		d.MinRetryDelay = DefaultRetryerMinRetryDelay
65	}
66	if d.MaxRetryDelay == 0 {
67		d.MaxRetryDelay = DefaultRetryerMaxRetryDelay
68	}
69	if d.MinThrottleDelay == 0 {
70		d.MinThrottleDelay = DefaultRetryerMinThrottleDelay
71	}
72	if d.MaxThrottleDelay == 0 {
73		d.MaxThrottleDelay = DefaultRetryerMaxThrottleDelay
74	}
75}
76
77// RetryRules returns the delay duration before retrying this request again
78func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
79
80	// if number of max retries is zero, no retries will be performed.
81	if d.NumMaxRetries == 0 {
82		return 0
83	}
84
85	// Sets default value for retryer members
86	d.setRetryerDefaults()
87
88	// minDelay is the minimum retryer delay
89	minDelay := d.MinRetryDelay
90
91	var initialDelay time.Duration
92
93	isThrottle := r.IsErrorThrottle()
94	if isThrottle {
95		if delay, ok := getRetryAfterDelay(r); ok {
96			initialDelay = delay
97		}
98		minDelay = d.MinThrottleDelay
99	}
100
101	retryCount := r.RetryCount
102
103	// maxDelay the maximum retryer delay
104	maxDelay := d.MaxRetryDelay
105
106	if isThrottle {
107		maxDelay = d.MaxThrottleDelay
108	}
109
110	var delay time.Duration
111
112	// Logic to cap the retry count based on the minDelay provided
113	actualRetryCount := int(math.Log2(float64(minDelay))) + 1
114	if actualRetryCount < 63-retryCount {
115		delay = time.Duration(1<<uint64(retryCount)) * getJitterDelay(minDelay)
116		if delay > maxDelay {
117			delay = getJitterDelay(maxDelay / 2)
118		}
119	} else {
120		delay = getJitterDelay(maxDelay / 2)
121	}
122	return delay + initialDelay
123}
124
125// getJitterDelay returns a jittered delay for retry
126func getJitterDelay(duration time.Duration) time.Duration {
127	return time.Duration(sdkrand.SeededRand.Int63n(int64(duration)) + int64(duration))
128}
129
130// ShouldRetry returns true if the request should be retried.
131func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
132
133	// ShouldRetry returns false if number of max retries is 0.
134	if d.NumMaxRetries == 0 {
135		return false
136	}
137
138	// If one of the other handlers already set the retry state
139	// we don't want to override it based on the service's state
140	if r.Retryable != nil {
141		return *r.Retryable
142	}
143	return r.IsErrorRetryable() || r.IsErrorThrottle()
144}
145
146// This will look in the Retry-After header, RFC 7231, for how long
147// it will wait before attempting another request
148func getRetryAfterDelay(r *request.Request) (time.Duration, bool) {
149	if !canUseRetryAfterHeader(r) {
150		return 0, false
151	}
152
153	delayStr := r.HTTPResponse.Header.Get("Retry-After")
154	if len(delayStr) == 0 {
155		return 0, false
156	}
157
158	delay, err := strconv.Atoi(delayStr)
159	if err != nil {
160		return 0, false
161	}
162
163	return time.Duration(delay) * time.Second, true
164}
165
166// Will look at the status code to see if the retry header pertains to
167// the status code.
168func canUseRetryAfterHeader(r *request.Request) bool {
169	switch r.HTTPResponse.StatusCode {
170	case 429:
171	case 503:
172	default:
173		return false
174	}
175
176	return true
177}
178