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  }