github.com/jfrog/jfrog-client-go@v1.40.2/utils/retryexecutor.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
     8  	"time"
     9  
    10  	"github.com/jfrog/jfrog-client-go/utils/log"
    11  )
    12  
    13  type ExecutionHandlerFunc func() (shouldRetry bool, err error)
    14  
    15  type RetryExecutor struct {
    16  	// The context
    17  	Context context.Context
    18  
    19  	// The amount of retries to perform.
    20  	MaxRetries int
    21  
    22  	// Number of milliseconds to sleep between retries.
    23  	RetriesIntervalMilliSecs int
    24  
    25  	// Message to display when retrying.
    26  	ErrorMessage string
    27  
    28  	// Prefix to print at the beginning of each log.
    29  	LogMsgPrefix string
    30  
    31  	// ExecutionHandler is the operation to run with retries.
    32  	ExecutionHandler ExecutionHandlerFunc
    33  }
    34  
    35  func (runner *RetryExecutor) Execute() error {
    36  	var err error
    37  	var shouldRetry bool
    38  	for i := 0; i <= runner.MaxRetries; i++ {
    39  		// Run ExecutionHandler
    40  		shouldRetry, err = runner.ExecutionHandler()
    41  
    42  		// If we should not retry, return.
    43  		if !shouldRetry {
    44  			return err
    45  		}
    46  		if cancelledErr := runner.checkCancelled(); cancelledErr != nil {
    47  			return cancelledErr
    48  		}
    49  
    50  		// Print retry log message
    51  		runner.LogRetry(i, err)
    52  
    53  		// Going to sleep for RetryInterval milliseconds
    54  		if runner.RetriesIntervalMilliSecs > 0 && i < runner.MaxRetries {
    55  			time.Sleep(time.Millisecond * time.Duration(runner.RetriesIntervalMilliSecs))
    56  		}
    57  	}
    58  	// If the error is not nil, return it and log the timeout message. Otherwise, generate new error.
    59  	if err != nil {
    60  		log.Info(runner.getTimeoutErrorMsg())
    61  		return err
    62  	}
    63  	return errorutils.CheckError(RetryExecutorTimeoutError{runner.getTimeoutErrorMsg()})
    64  }
    65  
    66  // Error of this type will be returned if the executor reaches timeout and no other error is returned by the execution handler.
    67  type RetryExecutorTimeoutError struct {
    68  	errMsg string
    69  }
    70  
    71  func (retryErr RetryExecutorTimeoutError) Error() string {
    72  	return retryErr.errMsg
    73  }
    74  
    75  func (runner *RetryExecutor) getTimeoutErrorMsg() string {
    76  	prefix := ""
    77  	if runner.LogMsgPrefix != "" {
    78  		prefix = runner.LogMsgPrefix + " "
    79  	}
    80  	return fmt.Sprintf("%sexecutor timeout after %v attempts with %v milliseconds wait intervals", prefix, runner.MaxRetries, runner.RetriesIntervalMilliSecs)
    81  }
    82  
    83  func (runner *RetryExecutor) LogRetry(attemptNumber int, err error) {
    84  	message := fmt.Sprintf("%s(Attempt %v)", runner.LogMsgPrefix, attemptNumber+1)
    85  	if runner.ErrorMessage != "" {
    86  		message = fmt.Sprintf("%s - %s", message, runner.ErrorMessage)
    87  	}
    88  	if err != nil {
    89  		message = fmt.Sprintf("%s: %s", message, err.Error())
    90  	}
    91  
    92  	if err != nil || runner.ErrorMessage != "" {
    93  		log.Warn(message)
    94  	} else {
    95  		log.Debug(message)
    96  	}
    97  }
    98  
    99  func (runner *RetryExecutor) checkCancelled() error {
   100  	if runner.Context == nil {
   101  		return nil
   102  	}
   103  	contextErr := runner.Context.Err()
   104  	if errors.Is(contextErr, context.Canceled) {
   105  		log.Info("Retry executor was cancelled")
   106  		return contextErr
   107  	}
   108  	return nil
   109  }