Skip to content

Commit 5d992dd

Browse files
author
Dean Karn
authored
Add error helpers (#10)
1 parent 3630896 commit 5d992dd

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

errors/retryable.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package errorsext
2+
3+
import (
4+
"errors"
5+
"strings"
6+
"syscall"
7+
)
8+
9+
// IsRetryableHTTP returns if the provided error is considered retryable HTTP error. It also returns the
10+
// type, in string form, for optional logging and metrics use.
11+
func IsRetryableHTTP(err error) (retryType string, isRetryable bool) {
12+
if retryType, isRetryable = IsRetryableNetwork(err); isRetryable {
13+
return
14+
}
15+
16+
errStr := err.Error()
17+
18+
if strings.Contains(errStr, "http2: server sent GOAWAY") {
19+
return "goaway", true
20+
}
21+
22+
// errServerClosedIdle is not seen by users for idempotent HTTP requests, but may be
23+
// seen by a user if the server shuts down an idle connection and sends its FIN
24+
// in flight with already-written POST body bytes from the client.
25+
// See https://github.com/golang/go/issues/19943#issuecomment-355607646
26+
//
27+
// This will possibly get fixed in the upstream SDK's based on the ability to set an HTTP error in the future
28+
// https://go-review.googlesource.com/c/go/+/191779/ but until then we should retry these.
29+
//
30+
if strings.Contains(errStr, "http: server closed idle connection") {
31+
return "server_close_idle_connection", true
32+
}
33+
return "", false
34+
}
35+
36+
// IsRetryableNetwork returns if the provided error is a retryable network related error. It also returns the
37+
// type, in string form, for optional logging and metrics use.
38+
func IsRetryableNetwork(err error) (retryType string, isRetryable bool) {
39+
if IsTemporary(err) {
40+
return "temporary", true
41+
}
42+
if IsTimeout(err) {
43+
return "timeout", true
44+
}
45+
return IsTemporaryConnection(err)
46+
}
47+
48+
// IsTemporary returns true if the provided error is considered retryable temporary error by testing if it
49+
// complies with an interface implementing `Temporary() bool` and calling the function.
50+
func IsTemporary(err error) bool {
51+
var t interface{ Temporary() bool }
52+
return errors.As(err, &t) && t.Temporary()
53+
}
54+
55+
// IsTimeout returns true if the provided error is considered a retryable timeout error by testing if it
56+
// complies with an interface implementing `Timeout() bool` and calling the function.
57+
func IsTimeout(err error) bool {
58+
var t interface{ Timeout() bool }
59+
return errors.As(err, &t) && t.Timeout()
60+
}
61+
62+
// IsTemporaryConnection returns if the provided error was a low level retryable connection error. It also returns the
63+
// type, in string form, for optional logging and metrics use.
64+
func IsTemporaryConnection(err error) (retryType string, isRetryable bool) {
65+
if err != nil {
66+
if errors.Is(err, syscall.ECONNRESET) {
67+
return "econnreset", true
68+
}
69+
if errors.Is(err, syscall.ECONNABORTED) {
70+
return "econnaborted", true
71+
}
72+
if errors.Is(err, syscall.ENOTCONN) {
73+
return "enotconn", true
74+
}
75+
if errors.Is(err, syscall.EWOULDBLOCK) {
76+
return "ewouldblock", true
77+
}
78+
if errors.Is(err, syscall.EAGAIN) {
79+
return "eagain", true
80+
}
81+
if errors.Is(err, syscall.ETIMEDOUT) {
82+
return "etimedout", true
83+
}
84+
if errors.Is(err, syscall.EINTR) {
85+
return "eintr", true
86+
}
87+
}
88+
return "", false
89+
}

net/http/retryable.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package httpext
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
var (
8+
// retryableStatusCodes defines the common HTTP response codes that are considered retryable.
9+
retryableStatusCodes = map[int]bool{
10+
http.StatusServiceUnavailable: true,
11+
http.StatusTooManyRequests: true,
12+
http.StatusBadGateway: true,
13+
http.StatusGatewayTimeout: true,
14+
15+
// 524 is a Cloudflare specific error which indicates it connected to the origin server but did not receive
16+
// response within 100 seconds and so times out.
17+
// https://support.cloudflare.com/hc/en-us/articles/115003011431-Error-524-A-timeout-occurred#524error
18+
524: true,
19+
}
20+
)
21+
22+
// IsRetryableStatusCode returns if the provided status code is considered retryable.
23+
func IsRetryableStatusCode(code int) bool {
24+
return retryableStatusCodes[code]
25+
}

0 commit comments

Comments
 (0)