github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/cos/err.go (about)

     1  // Package cos provides common low-level types and utilities for all aistore projects
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package cos
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"sync"
    16  	ratomic "sync/atomic"
    17  	"syscall"
    18  
    19  	"github.com/NVIDIA/aistore/cmn/debug"
    20  )
    21  
    22  type (
    23  	ErrNotFound struct {
    24  		where fmt.Stringer
    25  		what  string
    26  	}
    27  	ErrSignal struct {
    28  		signal syscall.Signal
    29  	}
    30  	Errs struct {
    31  		errs []error
    32  		cnt  int64
    33  		mu   sync.Mutex
    34  	}
    35  )
    36  
    37  var (
    38  	ErrQuantityUsage   = errors.New("invalid quantity, format should be '81%' or '1GB'")
    39  	ErrQuantityPercent = errors.New("percent must be in the range (0, 100)")
    40  	ErrQuantityBytes   = errors.New("value (bytes) must be non-negative")
    41  
    42  	errQuantityNonNegative = errors.New("quantity should not be negative")
    43  )
    44  
    45  var errBufferUnderrun = errors.New("buffer underrun")
    46  
    47  // ErrNotFound
    48  
    49  func NewErrNotFound(where fmt.Stringer, what string) *ErrNotFound {
    50  	return &ErrNotFound{where: where, what: what}
    51  }
    52  
    53  func (e *ErrNotFound) Error() string {
    54  	s := e.what + " does not exist"
    55  	if e.where == nil {
    56  		return s
    57  	}
    58  	return e.where.String() + ": " + s
    59  }
    60  
    61  func IsErrNotFound(err error) bool {
    62  	_, ok := err.(*ErrNotFound)
    63  	return ok
    64  }
    65  
    66  //
    67  // gen-purpose not-finding-anything: objects, directories, xactions, nodes, ...
    68  //
    69  
    70  func IsNotExist(err error, ecode int) bool {
    71  	if ecode == http.StatusNotFound || IsErrNotFound(err) {
    72  		return true
    73  	}
    74  	return os.IsNotExist(err) // unwraps for fs.ErrNotExist
    75  }
    76  
    77  // Errs
    78  // add Unwrap() if need be
    79  
    80  const maxErrs = 4
    81  
    82  func (e *Errs) Add(err error) {
    83  	debug.Assert(err != nil)
    84  	e.mu.Lock()
    85  	// first, check for duplication
    86  	for _, added := range e.errs {
    87  		if added.Error() == err.Error() {
    88  			e.mu.Unlock()
    89  			return
    90  		}
    91  	}
    92  	if len(e.errs) < maxErrs {
    93  		e.errs = append(e.errs, err)
    94  		ratomic.StoreInt64(&e.cnt, int64(len(e.errs)))
    95  	}
    96  	e.mu.Unlock()
    97  }
    98  
    99  func (e *Errs) Cnt() int { return int(ratomic.LoadInt64(&e.cnt)) }
   100  
   101  func (e *Errs) JoinErr() (cnt int, err error) {
   102  	if cnt = e.Cnt(); cnt > 0 {
   103  		e.mu.Lock()
   104  		err = errors.Join(e.errs...) // up to maxErrs
   105  		e.mu.Unlock()
   106  	}
   107  	return
   108  }
   109  
   110  // Errs is an error
   111  func (e *Errs) Error() (s string) {
   112  	var (
   113  		err error
   114  		cnt = e.Cnt()
   115  	)
   116  	if cnt == 0 {
   117  		return
   118  	}
   119  	e.mu.Lock()
   120  	if cnt = len(e.errs); cnt > 0 {
   121  		err = e.errs[0]
   122  	}
   123  	e.mu.Unlock()
   124  	if err == nil {
   125  		return // unlikely
   126  	}
   127  	if cnt > 1 {
   128  		err = fmt.Errorf("%v (and %d more error%s)", err, cnt-1, Plural(cnt-1))
   129  	}
   130  	s = err.Error()
   131  	return
   132  }
   133  
   134  //
   135  // IS-syscall helpers
   136  //
   137  
   138  func UnwrapSyscallErr(err error) error {
   139  	if syscallErr, ok := err.(*os.SyscallError); ok {
   140  		return syscallErr.Unwrap()
   141  	}
   142  	return nil
   143  }
   144  
   145  func IsErrSyscallTimeout(err error) bool {
   146  	syscallErr, ok := err.(*os.SyscallError)
   147  	return ok && syscallErr.Timeout()
   148  }
   149  
   150  // likely out of socket descriptors
   151  func IsErrConnectionNotAvail(err error) (yes bool) {
   152  	return errors.Is(err, syscall.EADDRNOTAVAIL)
   153  }
   154  
   155  // retriable conn errs
   156  func IsErrConnectionRefused(err error) (yes bool) { return errors.Is(err, syscall.ECONNREFUSED) }
   157  func IsErrConnectionReset(err error) (yes bool)   { return errors.Is(err, syscall.ECONNRESET) }
   158  func IsErrBrokenPipe(err error) (yes bool)        { return errors.Is(err, syscall.EPIPE) }
   159  
   160  func IsRetriableConnErr(err error) (yes bool) {
   161  	return IsErrConnectionRefused(err) || IsErrConnectionReset(err) || IsErrBrokenPipe(err)
   162  }
   163  
   164  func IsErrOOS(err error) bool {
   165  	return errors.Is(err, syscall.ENOSPC)
   166  }
   167  
   168  func isErrDNSLookup(err error) bool {
   169  	_, ok := err.(*net.DNSError)
   170  	return ok
   171  }
   172  
   173  func IsUnreachable(err error, status int) bool {
   174  	return IsErrConnectionRefused(err) ||
   175  		isErrDNSLookup(err) ||
   176  		errors.Is(err, context.DeadlineExceeded) ||
   177  		status == http.StatusRequestTimeout ||
   178  		status == http.StatusServiceUnavailable ||
   179  		IsEOF(err) ||
   180  		status == http.StatusBadGateway
   181  }
   182  
   183  //
   184  // ErrSignal
   185  //
   186  
   187  // https://tldp.org/LDP/abs/html/exitcodes.html
   188  func (e *ErrSignal) ExitCode() int               { return 128 + int(e.signal) }
   189  func NewSignalError(s syscall.Signal) *ErrSignal { return &ErrSignal{signal: s} }
   190  func (e *ErrSignal) Error() string               { return fmt.Sprintf("Signal %d", e.signal) }
   191  
   192  //
   193  // url.Error
   194  //
   195  
   196  func Err2ClientURLErr(err error) (uerr *url.Error) {
   197  	if e, ok := err.(*url.Error); ok {
   198  		uerr = e
   199  	}
   200  	return
   201  }
   202  
   203  func IsErrClientURLTimeout(err error) bool {
   204  	uerr := Err2ClientURLErr(err)
   205  	return uerr != nil && uerr.Timeout()
   206  }