github.com/grailbio/base@v0.0.11/errors/errors.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package errors implements an error type that defines standard
     6  // interpretable error codes for common error conditions. Errors also
     7  // contain interpretable severities, so that error-producing
     8  // operations can be retried in consistent ways. Errors returned by
     9  // this package can also be chained: thus attributing one error to
    10  // another. It is inspired by the error packages of both the Upspin
    11  // and Reflow projects, but generalizes and simplifies these.
    12  //
    13  // Errors are safely serialized with Gob, and can thus retain
    14  // semantics across process boundaries.
    15  //
    16  // TODO(marius): standardize translating AWS errors into *errors.Error.
    17  package errors
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/gob"
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"runtime"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"github.com/grailbio/base/log"
    31  	"v.io/v23/verror"
    32  )
    33  
    34  func init() {
    35  	gob.Register(new(Error))
    36  }
    37  
    38  // Separator defines the separation string inserted between
    39  // chained errors in error messages.
    40  var Separator = ":\n\t"
    41  
    42  // Kind defines the type of error. Kinds are semantically
    43  // meaningful, and may be interpreted by the receiver of an error
    44  // (e.g., to determine whether an operation should be retried).
    45  type Kind int
    46  
    47  const (
    48  	// Other indicates an unknown error.
    49  	Other Kind = iota
    50  	// Canceled indicates a context cancellation.
    51  	Canceled
    52  	// Timeout indicates an operation time out.
    53  	Timeout
    54  	// NotExist indicates a nonexistent resources.
    55  	NotExist
    56  	// NotAllowed indicates a permission failure.
    57  	NotAllowed
    58  	// NotSupported indicates an unsupported operation.
    59  	NotSupported
    60  	// Exists indicates that a resource already exists.
    61  	Exists
    62  	// Integrity indicates an integrity failure.
    63  	Integrity
    64  	// Unavailable indicates that a resource was unavailable.
    65  	Unavailable
    66  	// Invalid indicates that the caller supplied invalid parameters.
    67  	Invalid
    68  	// Net indicates a network error.
    69  	Net
    70  	// TooManyTries indicates a retry budget was exhausted.
    71  	TooManyTries
    72  	// Precondition indicates that a precondition was not met.
    73  	Precondition
    74  	// OOM indicates that an OOM condition was encountered.
    75  	OOM
    76  	// Remote indicates an error returned by an RPC, as distinct from errors in
    77  	// the machinery to execute the RPC, e.g. network issues, machine health,
    78  	// etc.
    79  	Remote
    80  	// ResourcesExhausted indicates that there were insufficient resources.
    81  	ResourcesExhausted
    82  
    83  	maxKind
    84  )
    85  
    86  var kinds = map[Kind]string{
    87  	Other:              "unknown error",
    88  	Canceled:           "operation was canceled",
    89  	Timeout:            "operation timed out",
    90  	NotExist:           "resource does not exist",
    91  	NotAllowed:         "access denied",
    92  	NotSupported:       "operation not supported",
    93  	Exists:             "resource already exists",
    94  	Integrity:          "integrity error",
    95  	Unavailable:        "resource unavailable",
    96  	Invalid:            "invalid argument",
    97  	Net:                "network error",
    98  	TooManyTries:       "too many tries",
    99  	Precondition:       "precondition failed",
   100  	OOM:                "out of memory",
   101  	Remote:             "remote error",
   102  	ResourcesExhausted: "resources exhausted",
   103  }
   104  
   105  // kindStdErrs maps some Kinds to the standard library's equivalent.
   106  var kindStdErrs = map[Kind]error{
   107  	Canceled:   context.Canceled,
   108  	Timeout:    context.DeadlineExceeded,
   109  	NotExist:   os.ErrNotExist,
   110  	NotAllowed: os.ErrPermission,
   111  	Exists:     os.ErrExist,
   112  	Invalid:    os.ErrInvalid,
   113  }
   114  
   115  // String returns a human-readable explanation of the error kind k.
   116  func (k Kind) String() string {
   117  	return kinds[k]
   118  }
   119  
   120  var kindErrnos = map[Kind]syscall.Errno{
   121  	Canceled:   syscall.EINTR,
   122  	Timeout:    syscall.ETIMEDOUT,
   123  	NotExist:   syscall.ENOENT,
   124  	NotAllowed: syscall.EACCES,
   125  	// We map to ENOTSUP instead of ENOSYS, as ENOTSUP is more granular,
   126  	// signifying that there may be configurations of a functionality that may
   127  	// be supported. ENOSYS, in contrast, signals that an entire
   128  	// function(ality) is not supported. If we need to express the distinction
   129  	// in the future, we can add a new kind.
   130  	NotSupported: syscall.ENOTSUP,
   131  	Exists:       syscall.EEXIST,
   132  	Unavailable:  syscall.EAGAIN,
   133  	Invalid:      syscall.EINVAL,
   134  	Net:          syscall.ENETUNREACH,
   135  	TooManyTries: syscall.EINVAL,
   136  	Precondition: syscall.EAGAIN,
   137  	OOM:          syscall.ENOMEM,
   138  	Remote:       syscall.EREMOTE,
   139  }
   140  
   141  // Errno maps k to an equivalent Errno or returns false if there's no good match.
   142  func (k Kind) Errno() (syscall.Errno, bool) {
   143  	errno, ok := kindErrnos[k]
   144  	return errno, ok
   145  }
   146  
   147  // Severity defines an Error's severity. An Error's severity determines
   148  // whether an error-producing operation may be retried or not.
   149  type Severity int
   150  
   151  const (
   152  	// Retriable indicates that the failing operation can be safely retried,
   153  	// regardless of application context.
   154  	Retriable Severity = -2
   155  	// Temporary indicates that the underlying error condition is likely
   156  	// temporary, and can be possibly be retried. However, such errors
   157  	// should be retried in an application specific context.
   158  	Temporary Severity = -1
   159  	// Unknown indicates the error's severity is unknown. This is the default
   160  	// severity level.
   161  	Unknown Severity = 0
   162  	// Fatal indicates that the underlying error condition is unrecoverable;
   163  	// retrying is unlikely to help.
   164  	Fatal Severity = 1
   165  )
   166  
   167  var severities = map[Severity]string{
   168  	Retriable: "retriable",
   169  	Temporary: "temporary",
   170  	Unknown:   "unknown",
   171  	Fatal:     "fatal",
   172  }
   173  
   174  // String returns a human-readable explanation of the error severity s.
   175  func (s Severity) String() string {
   176  	return severities[s]
   177  }
   178  
   179  // Error is the standard error type, carrying a kind (error code),
   180  // message (error message), and potentially an underlying error.
   181  // Errors should be constructed by errors.E, which interprets
   182  // arguments according to a set of rules.
   183  //
   184  // Errors may be serialized and deserialized with gob. When this is
   185  // done, underlying errors do not survive in full fidelity: they are
   186  // converted to their error strings and returned as opaque errors.
   187  type Error struct {
   188  	// Kind is the error's type.
   189  	Kind Kind
   190  	// Severity is an optional severity.
   191  	Severity Severity
   192  	// Message is an optional error message associated with this error.
   193  	Message string
   194  	// Err is the error that caused this error, if any.
   195  	// Errors can form chains through Err: the full chain is printed
   196  	// by Error().
   197  	Err error
   198  }
   199  
   200  // E constructs a new errors from the provided arguments. It is meant
   201  // as a convenient way to construct, annotate, and wrap errors.
   202  //
   203  // Arguments are interpreted according to their types:
   204  //
   205  //	- Kind: sets the Error's kind
   206  //	- Severity: set the Error's severity
   207  //	- string: sets the Error's message; multiple strings are
   208  //	  separated by a single space
   209  //	- *Error: copies the error and sets the error's cause
   210  //	- error: sets the Error's cause
   211  //
   212  // If an unrecognized argument type is encountered, an error with
   213  // kind Invalid is returned.
   214  //
   215  // If a kind is not provided, but an underlying error is, E attempts to
   216  // interpret the underlying error according to a set of conventions,
   217  // in order:
   218  //
   219  //	- If the os.IsNotExist(error) returns true, its kind is set to NotExist.
   220  // 	- If the error is context.Canceled, its kind is set to Canceled.
   221  //	- If the error implements interface { Timeout() bool } and
   222  //	Timeout() returns true, then its kind is set to Timeout
   223  //	- If the error implements interface { Temporary() bool } and
   224  //	Temporary() returns true, then its severity is set to at least
   225  //	Temporary.
   226  //
   227  // If the underlying error is another *Error, and a kind is not provided,
   228  // the returned error inherits that error's kind.
   229  func E(args ...interface{}) error {
   230  	if len(args) == 0 {
   231  		panic("no args")
   232  	}
   233  	e := new(Error)
   234  	var msg strings.Builder
   235  	for _, arg := range args {
   236  		switch arg := arg.(type) {
   237  		case Kind:
   238  			e.Kind = arg
   239  		case Severity:
   240  			e.Severity = arg
   241  		case string:
   242  			if msg.Len() > 0 {
   243  				msg.WriteString(" ")
   244  			}
   245  			msg.WriteString(arg)
   246  		case *Error:
   247  			copy := *arg
   248  			if len(args) == 1 {
   249  				// In this case, we're not adding anything new;
   250  				// just return the copy.
   251  				return &copy
   252  			}
   253  			e.Err = &copy
   254  		case error:
   255  			e.Err = arg
   256  		default:
   257  			_, file, line, _ := runtime.Caller(1)
   258  			log.Error.Printf("errors.E: bad call (type %T) from %s:%d: %v", arg, file, line, arg)
   259  			return &Error{
   260  				Kind:    Invalid,
   261  				Message: fmt.Sprintf("unknown type %T, value %v in error call", arg, arg),
   262  			}
   263  		}
   264  	}
   265  	e.Message = msg.String()
   266  	if e.Err == nil {
   267  		return e
   268  	}
   269  	switch prev := e.Err.(type) {
   270  	case *Error:
   271  		// TODO(marius): consider collapsing e.Err altogether if
   272  		// all we're doing is adding a Kind and/or Severity.
   273  		if prev.Kind == e.Kind || e.Kind == Other {
   274  			e.Kind = prev.Kind
   275  			prev.Kind = Other
   276  		}
   277  		if prev.Severity == e.Severity || e.Severity == Unknown {
   278  			e.Severity = prev.Severity
   279  			prev.Severity = Unknown
   280  		}
   281  	default:
   282  		// Classify common error types.
   283  		if err, ok := e.Err.(interface {
   284  			Temporary() bool
   285  		}); ok && err.Temporary() && e.Severity == Unknown {
   286  			e.Severity = Temporary
   287  		}
   288  		if e.Kind != Other {
   289  			break
   290  		}
   291  		// Note: Loop over kind instead of kindStdErrs for determinism.
   292  		for kind := Kind(0); kind < maxKind; kind++ {
   293  			stdErr := kindStdErrs[kind]
   294  			if stdErr != nil && errors.Is(e.Err, stdErr) {
   295  				e.Kind = kind
   296  				break
   297  			}
   298  		}
   299  		if e.Kind != Other {
   300  			break
   301  		}
   302  		// Interpret verror errors.
   303  		if err, ok := asVerrorE(e.Err); ok {
   304  			// TODO: Kill this workaround for chained ErrNoAccess errors. See
   305  			// https://github.com/vanadium/core/pull/282 .  Once we upgrade
   306  			// verror to a version whose error.Is supports matching of chained
   307  			// errors, we can kill the string check.
   308  			//
   309  			// Separately, we can consider expanding to map more verror error
   310  			// types.
   311  			if errors.Is(err, verror.ErrNoAccess) ||
   312  				strings.Contains(err.Error(), string(verror.ErrNoAccess.ID)) {
   313  				e.Kind = NotAllowed
   314  			}
   315  		}
   316  		if e.Kind != Other {
   317  			break
   318  		}
   319  		if isTimeoutErr(e.Err) {
   320  			e.Kind = Timeout
   321  		}
   322  	}
   323  	return e
   324  }
   325  
   326  func isTimeoutErr(err error) bool {
   327  	t, ok := err.(interface{ Timeout() bool })
   328  	return ok && t.Timeout()
   329  }
   330  
   331  // Recover recovers any error into an *Error. If the passed-in Error is already
   332  // an error, it is simply returned; otherwise it is wrapped in an error.
   333  func Recover(err error) *Error {
   334  	if err == nil {
   335  		return nil
   336  	}
   337  	if err, ok := err.(*Error); ok {
   338  		return err
   339  	}
   340  	return E(err).(*Error)
   341  }
   342  
   343  // Error returns a human readable string describing this error.
   344  // It uses the separator defined by errors.Separator.
   345  func (e *Error) Error() string {
   346  	if e == nil {
   347  		return "<nil>"
   348  	}
   349  	var b bytes.Buffer
   350  	e.writeError(&b)
   351  	return b.String()
   352  }
   353  
   354  func (e *Error) writeError(b *bytes.Buffer) {
   355  	if e.Message != "" {
   356  		pad(b, ": ")
   357  		b.WriteString(e.Message)
   358  	}
   359  	if e.Kind != Other {
   360  		pad(b, ": ")
   361  		b.WriteString(e.Kind.String())
   362  	}
   363  	if e.Severity != Unknown {
   364  		pad(b, " ")
   365  		b.WriteByte('(')
   366  		b.WriteString(e.Severity.String())
   367  		b.WriteByte(')')
   368  	}
   369  
   370  	if e.Err == nil {
   371  		return
   372  	}
   373  	if err, ok := e.Err.(*Error); ok {
   374  		pad(b, Separator)
   375  		b.WriteString(err.Error())
   376  	} else {
   377  		pad(b, ": ")
   378  		b.WriteString(e.Err.Error())
   379  	}
   380  }
   381  
   382  // Timeout tells whether this error is a timeout error.
   383  func (e *Error) Timeout() bool {
   384  	return e.Kind == Timeout
   385  }
   386  
   387  // Temporary tells whether this error is temporary.
   388  func (e *Error) Temporary() bool {
   389  	return e.Severity <= Temporary
   390  }
   391  
   392  // Unwrap returns e's cause, if any, or nil. It lets the standard library's
   393  // errors.Unwrap work with *Error.
   394  func (e *Error) Unwrap() error {
   395  	return e.Err
   396  }
   397  
   398  // Is tells whether e.Kind is equivalent to err.
   399  //
   400  // This implements interoperability with the standard library's errors.Is:
   401  //   errors.Is(e, errors.Canceled)
   402  // works if e.Kind corresponds (in this example, Canceled). This is useful when
   403  // passing *Error to third-party libraries, for example. Users should still
   404  // prefer this package's Is for their own tests because it's less prone to error
   405  // (type checking disallows accidentally swapped arguments).
   406  //
   407  // Note: This match does not recurse into err's cause, if any; see the standard
   408  // library's errors.Is for how this is used.
   409  func (e *Error) Is(err error) bool {
   410  	if err == nil {
   411  		return false
   412  	}
   413  	if err == kindStdErrs[e.Kind] {
   414  		return true
   415  	}
   416  	if e.Kind == Timeout && isTimeoutErr(err) {
   417  		return true
   418  	}
   419  	return false
   420  }
   421  
   422  type gobError struct {
   423  	Kind     Kind
   424  	Severity Severity
   425  	Message  string
   426  	Next     *gobError
   427  	Err      string
   428  }
   429  
   430  func (ge *gobError) toError() *Error {
   431  	e := &Error{
   432  		Kind:     ge.Kind,
   433  		Severity: ge.Severity,
   434  		Message:  ge.Message,
   435  	}
   436  	if ge.Next != nil {
   437  		e.Err = ge.Next.toError()
   438  	} else if ge.Err != "" {
   439  		e.Err = errors.New(ge.Err)
   440  	}
   441  	return e
   442  }
   443  
   444  func (e *Error) toGobError() *gobError {
   445  	ge := &gobError{
   446  		Kind:     e.Kind,
   447  		Severity: e.Severity,
   448  		Message:  e.Message,
   449  	}
   450  	if e.Err == nil {
   451  		return ge
   452  	}
   453  	switch arg := e.Err.(type) {
   454  	case *Error:
   455  		ge.Next = arg.toGobError()
   456  	default:
   457  		ge.Err = arg.Error()
   458  	}
   459  	return ge
   460  }
   461  
   462  // GobEncode encodes the error for gob. Since underlying errors may
   463  // be interfaces unknown to gob, Error's gob encoding replaces these
   464  // with error strings.
   465  func (e *Error) GobEncode() ([]byte, error) {
   466  	var b bytes.Buffer
   467  	err := gob.NewEncoder(&b).Encode(e.toGobError())
   468  	return b.Bytes(), err
   469  }
   470  
   471  // GobDecode decodes an error encoded by GobEncode.
   472  func (e *Error) GobDecode(p []byte) error {
   473  	var ge gobError
   474  	if err := gob.NewDecoder(bytes.NewBuffer(p)).Decode(&ge); err != nil {
   475  		return err
   476  	}
   477  	*e = *ge.toError()
   478  	return nil
   479  }
   480  
   481  // Is tells whether an error has a specified kind, except for the
   482  // indeterminate kind Other. In the case an error has kind Other, the
   483  // chain is traversed until a non-Other error is encountered.
   484  //
   485  // This is similar to the standard library's errors.Is(err, target). That
   486  // traverses err's chain looking for one that matches target, where target may
   487  // be os.ErrNotExist, etc. *Error has an explicit Kind instead of an error-typed
   488  // target, but (*Error).Is defines an error -> Kind relationship to allow
   489  // interoperability.
   490  func Is(kind Kind, err error) bool {
   491  	if err == nil {
   492  		return false
   493  	}
   494  	return is(kind, Recover(err))
   495  }
   496  
   497  func is(kind Kind, e *Error) bool {
   498  	if e.Kind != Other {
   499  		return e.Kind == kind
   500  	}
   501  	if e.Err != nil {
   502  		if e2, ok := e.Err.(*Error); ok {
   503  			return is(kind, e2)
   504  		}
   505  	}
   506  	return false
   507  }
   508  
   509  // IsTemporary tells whether the provided error is likely temporary.
   510  func IsTemporary(err error) bool {
   511  	return Recover(err).Temporary()
   512  }
   513  
   514  // Match tells whether every nonempty field in err1
   515  // matches the corresponding fields in err2. The comparison
   516  // recurses on chained errors. Match is designed to aid in
   517  // testing errors.
   518  func Match(err1, err2 error) bool {
   519  	var (
   520  		e1 = Recover(err1)
   521  		e2 = Recover(err2)
   522  	)
   523  	if e1.Kind != Other && e1.Kind != e2.Kind {
   524  		return false
   525  	}
   526  	if e1.Severity != Unknown && e1.Severity != e2.Severity {
   527  		return false
   528  	}
   529  	if e1.Message != "" && e1.Message != e2.Message {
   530  		return false
   531  	}
   532  	if e1.Err != nil {
   533  		if e2.Err == nil {
   534  			return false
   535  		}
   536  		switch e1.Err.(type) {
   537  		case *Error:
   538  			return Match(e1.Err, e2.Err)
   539  		default:
   540  			return e1.Err.Error() == e2.Err.Error()
   541  		}
   542  	}
   543  	return true
   544  }
   545  
   546  // Visit calls the given function for every error object in the chain, including
   547  // itself.  Recursion stops after the function finds an error object of type
   548  // other than *Error.
   549  func Visit(err error, callback func(err error)) {
   550  	callback(err)
   551  	for {
   552  		next, ok := err.(*Error)
   553  		if !ok {
   554  			break
   555  		}
   556  		err = next.Err
   557  		callback(err)
   558  	}
   559  }
   560  
   561  // New is synonymous with errors.New, and is provided here so that
   562  // users need only import one errors package.
   563  func New(msg string) error {
   564  	return errors.New(msg)
   565  }
   566  
   567  func pad(b *bytes.Buffer, s string) {
   568  	if b.Len() == 0 {
   569  		return
   570  	}
   571  	b.WriteString(s)
   572  }
   573  
   574  func asVerrorE(err error) (verror.E, bool) {
   575  	switch e := err.(type) {
   576  	case verror.E:
   577  		return e, true
   578  	case *verror.E:
   579  		if e != nil {
   580  			return *e, true
   581  		}
   582  	}
   583  	return verror.E{}, false
   584  }