-
Notifications
You must be signed in to change notification settings - Fork 81
/
retries.go
105 lines (86 loc) · 3.16 KB
/
retries.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package linodego
import (
"errors"
"log"
"net/http"
"strconv"
"time"
"github.com/go-resty/resty/v2"
"golang.org/x/net/http2"
)
const (
retryAfterHeaderName = "Retry-After"
maintenanceModeHeaderName = "X-Maintenance-Mode"
defaultRetryCount = 1000
)
// type RetryConditional func(r *resty.Response) (shouldRetry bool)
type RetryConditional resty.RetryConditionFunc
// type RetryAfter func(c *resty.Client, r *resty.Response) (time.Duration, error)
type RetryAfter resty.RetryAfterFunc
// Configures resty to
// lock until enough time has passed to retry the request as determined by the Retry-After response header.
// If the Retry-After header is not set, we fall back to value of SetPollDelay.
func configureRetries(c *Client) {
c.resty.
SetRetryCount(defaultRetryCount).
AddRetryCondition(checkRetryConditionals(c)).
SetRetryAfter(respectRetryAfter)
}
func checkRetryConditionals(c *Client) func(*resty.Response, error) bool {
return func(r *resty.Response, err error) bool {
for _, retryConditional := range c.retryConditionals {
retry := retryConditional(r, err)
if retry {
log.Printf("[INFO] Received error %s - Retrying", r.Error())
return true
}
}
return false
}
}
// SetLinodeBusyRetry configures resty to retry specifically on "Linode busy." errors
// The retry wait time is configured in SetPollDelay
func linodeBusyRetryCondition(r *resty.Response, _ error) bool {
apiError, ok := r.Error().(*APIError)
linodeBusy := ok && apiError.Error() == "Linode busy."
retry := r.StatusCode() == http.StatusBadRequest && linodeBusy
return retry
}
func tooManyRequestsRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusTooManyRequests
}
func serviceUnavailableRetryCondition(r *resty.Response, _ error) bool {
serviceUnavailable := r.StatusCode() == http.StatusServiceUnavailable
// During maintenance events, the API will return a 503 and add
// an `X-MAINTENANCE-MODE` header. Don't retry during maintenance
// events, only for legitimate 503s.
if serviceUnavailable && r.Header().Get(maintenanceModeHeaderName) != "" {
log.Printf("[INFO] Linode API is under maintenance, request will not be retried - please see status.linode.com for more information")
return false
}
return serviceUnavailable
}
func requestTimeoutRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusRequestTimeout
}
func requestGOAWAYRetryCondition(_ *resty.Response, e error) bool {
return errors.As(e, &http2.GoAwayError{})
}
func requestNGINXRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusBadRequest &&
r.Header().Get("Server") == "nginx" &&
r.Header().Get("Content-Type") == "text/html"
}
func respectRetryAfter(client *resty.Client, resp *resty.Response) (time.Duration, error) {
retryAfterStr := resp.Header().Get(retryAfterHeaderName)
if retryAfterStr == "" {
return 0, nil
}
retryAfter, err := strconv.Atoi(retryAfterStr)
if err != nil {
return 0, err
}
duration := time.Duration(retryAfter) * time.Second
log.Printf("[INFO] Respecting Retry-After Header of %d (%s) (max %s)", retryAfter, duration, client.RetryMaxWaitTime)
return duration, nil
}