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