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 }