github.com/pelicanplatform/pelican@v1.0.5/client/errorAccum.go (about) 1 /*************************************************************** 2 * 3 * Copyright (C) 2023, University of Nebraska-Lincoln 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); you 6 * may not use this file except in compliance with the License. You may 7 * obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ***************************************************************/ 18 19 package client 20 21 import ( 22 "errors" 23 "fmt" 24 "net/http" 25 "sync" 26 "time" 27 28 grab "github.com/opensaucerer/grab/v3" 29 ) 30 31 type TimestampedError struct { 32 err error 33 timestamp time.Time 34 } 35 36 var ( 37 bunchOfErrors []TimestampedError 38 mu sync.Mutex 39 // We will generate an error string including the time since startup 40 startup time.Time = time.Now() 41 ) 42 43 // AddError will add an accumulated error to the error stack 44 func AddError(err error) bool { 45 mu.Lock() 46 defer mu.Unlock() 47 bunchOfErrors = append(bunchOfErrors, TimestampedError{err, time.Now()}) 48 return true 49 } 50 51 func ClearErrors() { 52 mu.Lock() 53 defer mu.Unlock() 54 55 bunchOfErrors = make([]TimestampedError, 0) 56 } 57 58 func GetErrors() string { 59 mu.Lock() 60 defer mu.Unlock() 61 first := true 62 lastError := startup 63 var errorsFormatted []string 64 for idx, theError := range bunchOfErrors { 65 errFmt := fmt.Sprintf("Attempt #%v: %s", idx+1, theError.err.Error()) 66 timeElapsed := theError.timestamp.Sub(lastError) 67 timeFormat := timeElapsed.Truncate(100 * time.Millisecond).String() 68 errFmt += " (" + timeFormat 69 if first { 70 errFmt += " since start)" 71 } else { 72 timeSinceStart := theError.timestamp.Sub(startup) 73 timeSinceStartFormat := timeSinceStart.Truncate(100 * time.Millisecond).String() 74 errFmt += " elapsed, " + timeSinceStartFormat + " since start)" 75 } 76 lastError = theError.timestamp 77 errorsFormatted = append(errorsFormatted, errFmt) 78 first = false 79 } 80 var toReturn string 81 first = true 82 for idx := len(errorsFormatted) - 1; idx >= 0; idx-- { 83 if !first { 84 toReturn += "; " 85 } 86 toReturn += errorsFormatted[idx] 87 first = false 88 } 89 return toReturn 90 } 91 92 // IsRetryable will return true if the error is retryable 93 func IsRetryable(err error) bool { 94 if errors.Is(err, &SlowTransferError{}) { 95 return true 96 } 97 if errors.Is(err, grab.ErrBadLength) { 98 return false 99 } 100 var cse *ConnectionSetupError 101 if errors.As(err, &cse) { 102 if sce, ok := cse.Unwrap().(grab.StatusCodeError); ok { 103 switch int(sce) { 104 case http.StatusInternalServerError: 105 case http.StatusBadGateway: 106 case http.StatusServiceUnavailable: 107 case http.StatusGatewayTimeout: 108 return true 109 default: 110 return false 111 } 112 } 113 return true 114 } 115 var hep *HttpErrResp 116 if errors.As(err, &hep) { 117 switch int(hep.Code) { 118 case http.StatusInternalServerError: 119 case http.StatusBadGateway: 120 case http.StatusServiceUnavailable: 121 case http.StatusGatewayTimeout: 122 return true 123 default: 124 return false 125 } 126 } 127 return false 128 } 129 130 // ErrorsRetryable returns if the errors in the stack are retryable later 131 func ErrorsRetryable() bool { 132 mu.Lock() 133 defer mu.Unlock() 134 // Loop through the errors and see if all of them are retryable 135 for _, theError := range bunchOfErrors { 136 if !IsRetryable(theError.err) { 137 return false 138 } 139 } 140 return true 141 }