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  }