| 
 | 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 | +}  | 
0 commit comments