github.com/htcondor/osdf-client/v6@v6.13.0-rc1.0.20231009141709-766e7b4d1dc8/errorAccum.go (about)

     1  package stashcp
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"sync"
     8  	"time"
     9  
    10  	grab "github.com/cavaliercoder/grab"
    11  )
    12  
    13  type TimestampedError struct {
    14  	err error
    15  	timestamp time.Time
    16  }
    17  
    18  var (
    19  	bunchOfErrors []TimestampedError
    20  	mu            sync.Mutex
    21  	// We will generate an error string including the time since startup
    22  	startup       time.Time = time.Now()
    23  )
    24  
    25  // AddError will add an accumulated error to the error stack
    26  func AddError(err error) bool {
    27  	mu.Lock()
    28  	defer mu.Unlock()
    29  	bunchOfErrors = append(bunchOfErrors, TimestampedError{err, time.Now()})
    30  	return true
    31  }
    32  
    33  func ClearErrors() {
    34  	mu.Lock()
    35  	defer mu.Unlock()
    36  
    37  	bunchOfErrors = make([]TimestampedError, 0)
    38  }
    39  
    40  func GetErrors() string {
    41  	mu.Lock()
    42  	defer mu.Unlock()
    43  	first := true
    44  	lastError := startup
    45  	var errorsFormatted []string
    46  	for idx, theError := range bunchOfErrors {
    47  		errFmt := fmt.Sprintf("Attempt #%v: %s", idx + 1, theError.err.Error())
    48  		timeElapsed := theError.timestamp.Sub(lastError)
    49  		timeFormat := timeElapsed.Truncate(100*time.Millisecond).String()
    50  		errFmt += " (" + timeFormat
    51  		if first {
    52  			errFmt += " since start)"
    53  		} else {
    54  			timeSinceStart := theError.timestamp.Sub(startup)
    55  			timeSinceStartFormat := timeSinceStart.Truncate(100*time.Millisecond).String()
    56  			errFmt += " elapsed, " + timeSinceStartFormat + " since start)"
    57  		}
    58  		lastError = theError.timestamp
    59  		errorsFormatted = append(errorsFormatted, errFmt)
    60  		first = false
    61  	}
    62  	var toReturn string
    63  	first = true
    64  	for idx := len(errorsFormatted)-1; idx >= 0; idx-- {
    65  		if !first {
    66  			toReturn += "; "
    67  		}
    68  		toReturn += errorsFormatted[idx]
    69  		first = false
    70  	}
    71  	return toReturn
    72  }
    73  
    74  // IsRetryable will return true if the error is retryable
    75  func IsRetryable(err error) bool {
    76  	if errors.Is(err, &SlowTransferError{}) {
    77  		return true
    78  	}
    79  	var cse *ConnectionSetupError
    80  	if errors.As(err, &cse) {
    81  		if sce, ok := cse.Unwrap().(grab.StatusCodeError); ok {
    82  			switch int(sce) {
    83  			case http.StatusInternalServerError:
    84  			case http.StatusBadGateway:
    85  			case http.StatusServiceUnavailable:
    86  			case http.StatusGatewayTimeout:
    87  				return true
    88  			default:
    89  				return false
    90  			}
    91  		}
    92  		return true
    93  	}
    94  	return false
    95  }
    96  
    97  // ErrorsRetryable returns if the errors in the stack are retryable later
    98  func ErrorsRetryable() bool {
    99  	mu.Lock()
   100  	defer mu.Unlock()
   101  	// Loop through the errors and see if all of them are retryable
   102  	for _, theError := range bunchOfErrors {
   103  		if !IsRetryable(theError.err) {
   104  			return false
   105  		}
   106  	}
   107  	return true
   108  }