-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathretry.go
More file actions
136 lines (118 loc) · 3.62 KB
/
retry.go
File metadata and controls
136 lines (118 loc) · 3.62 KB
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package requesthelper
import (
"math"
"net/http"
"time"
"github.com/Azure/azure-extension-platform/pkg/logging"
)
// SleepFunc pauses the execution for at least duration d.
type SleepFunc func(d time.Duration)
var (
// ActualSleep uses actual time to pause the execution.
ActualSleep SleepFunc = time.Sleep
)
const (
// time to sleep between retries is an exponential backoff formula:
// t(n) = k * m^n
expRetryN = 7 // how many times we retry the Download
expRetryK = time.Second * 3
expRetryM = 2
)
// retryRequest is a shared function for retrying HTTP requests with exponential backoff.
func retryRequest(
el *logging.ExtensionLogger,
sf SleepFunc,
requestFunc func() (*http.Response, error),
warnMsg string,
infoPrefix string,
) (*http.Response, error) {
var lastErr error
for n := range expRetryN {
resp, err := requestFunc()
if err == nil {
return resp, nil
}
lastErr = err
el.Warn(warnMsg, err)
status := -1
if resp != nil {
if resp.Body != nil { // we are not going to read this response body
resp.Body.Close()
}
status = resp.StatusCode
}
// status == -1 the value when there was no http request
if status == -1 {
te, haste := lastErr.(interface {
Temporary() bool
})
to, hasto := lastErr.(interface {
Timeout() bool
})
if haste || hasto {
if haste && te.Temporary() {
el.Info("%sTemporary error occurred. Retrying: %v", infoPrefix, lastErr)
} else if hasto && to.Timeout() {
el.Info("%sTimeout error occurred. Retrying: %v", infoPrefix, lastErr)
} else {
el.Info("%sNon-timeout, non-temporary error occurred, skipping retries: %v", infoPrefix, lastErr)
break
}
} else {
el.Info("%sNo response returned and unexpected error, skipping retries", infoPrefix)
break
}
} else if !isTransientHTTPStatusCode(status) {
el.Info("%sRequest returned %v, skipping retries", infoPrefix, status)
break
}
if n != expRetryN-1 {
// have more retries to go, sleep before retrying
slp := expRetryK * time.Duration(int(math.Pow(float64(expRetryM), float64(n))))
sf(slp)
}
}
return nil, lastErr
}
// WithRetries retrieves a response body using the specified downloader. Any
// error returned from d will be retried (and retrieved response bodies will be
// closed on failures). If the retries do not succeed, the last error is returned.
//
// It sleeps in exponentially increasing durations between retries.
func WithRetries(el *logging.ExtensionLogger, rm *RequestManager, sf SleepFunc) (*http.Response, error) {
return retryRequest(
el,
sf,
rm.MakeRequest,
"error: %v",
"",
)
}
func isTransientHTTPStatusCode(statusCode int) bool {
switch statusCode {
case
http.StatusNotFound, // 404. This will occur if there is a timing issue with the NodeService CCF cache
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout: // 504
return true // timeout and too many requests
default:
return false
}
}
// WithRetriesArc retrieves a response body using the Arc authentication handler with retries.
// It handles the Arc challenge-response flow and retries on transient errors.
func WithRetriesArc(el *logging.ExtensionLogger, arcHandler *ArcAuthHandler, sf SleepFunc) (*http.Response, error) {
return retryRequest(
el,
sf,
func() (*http.Response, error) { return arcHandler.MakeArcRequest(el) },
"Arc request error: %v",
"Arc request: ",
)
}