github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/retry.go (about)

     1  // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
     2  // resty source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package resty
     6  
     7  import (
     8  	"context"
     9  	"math"
    10  	"math/rand"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  const (
    16  	defaultMaxRetries  = 3
    17  	defaultWaitTime    = time.Duration(100) * time.Millisecond
    18  	defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
    19  )
    20  
    21  type (
    22  	// Option is to create convenient retry options like wait time, max retries, etc.
    23  	Option func(*Options)
    24  
    25  	// RetryConditionFunc type is for retry condition function
    26  	// input: non-nil Response OR request execution error
    27  	RetryConditionFunc func(*Response, error) bool
    28  
    29  	// OnRetryFunc is for side-effecting functions triggered on retry
    30  	OnRetryFunc func(*Response, error)
    31  
    32  	// RetryAfterFunc returns time to wait before retry
    33  	// For example, it can parse HTTP Retry-After header
    34  	// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    35  	// Non-nil error is returned if it is found that request is not retryable
    36  	// (0, nil) is a special result means 'use default algorithm'
    37  	RetryAfterFunc func(*Client, *Response) (time.Duration, error)
    38  
    39  	// Options struct is used to hold retry settings.
    40  	Options struct {
    41  		maxRetries      int
    42  		waitTime        time.Duration
    43  		maxWaitTime     time.Duration
    44  		retryConditions []RetryConditionFunc
    45  		retryHooks      []OnRetryFunc
    46  	}
    47  )
    48  
    49  // Retries sets the max number of retries
    50  func Retries(v int) Option { return func(o *Options) { o.maxRetries = v } }
    51  
    52  // WaitTime sets the default wait time to sleep between requests
    53  func WaitTime(v time.Duration) Option { return func(o *Options) { o.waitTime = v } }
    54  
    55  // MaxWaitTime sets the max wait time to sleep between requests
    56  func MaxWaitTime(v time.Duration) Option { return func(o *Options) { o.maxWaitTime = v } }
    57  
    58  // RetryConditions sets the conditions that will be checked for retry.
    59  func RetryConditions(conditions []RetryConditionFunc) Option {
    60  	return func(o *Options) {
    61  		o.retryConditions = conditions
    62  	}
    63  }
    64  
    65  // RetryHooks sets the hooks that will be executed after each retry
    66  func RetryHooks(hooks []OnRetryFunc) Option {
    67  	return func(o *Options) {
    68  		o.retryHooks = hooks
    69  	}
    70  }
    71  
    72  // Backoff retries with increasing timeout duration up until X amount of retries
    73  // (Default is 3 attempts, Override with option Retries(n))
    74  func Backoff(operation func() (*Response, error), options ...Option) error {
    75  	// Defaults
    76  	opts := Options{
    77  		maxRetries:      defaultMaxRetries,
    78  		waitTime:        defaultWaitTime,
    79  		maxWaitTime:     defaultMaxWaitTime,
    80  		retryConditions: []RetryConditionFunc{},
    81  	}
    82  
    83  	for _, o := range options {
    84  		o(&opts)
    85  	}
    86  
    87  	var resp *Response
    88  	var err error
    89  
    90  	for attempt := 0; attempt <= opts.maxRetries; attempt++ {
    91  		resp, err = operation()
    92  		ctx := context.Background()
    93  		if resp != nil && resp.Request.ctx != nil {
    94  			ctx = resp.Request.ctx
    95  		}
    96  		if ctx.Err() != nil {
    97  			return err
    98  		}
    99  
   100  		err1 := unwrapNoRetryErr(err)           // raw error, it used for return users callback.
   101  		needsRetry := err != nil && err == err1 // retry on a few operation errors by default
   102  
   103  		for _, condition := range opts.retryConditions {
   104  			needsRetry = condition(resp, err1)
   105  			if needsRetry {
   106  				break
   107  			}
   108  		}
   109  
   110  		if !needsRetry {
   111  			return err
   112  		}
   113  
   114  		for _, hook := range opts.retryHooks {
   115  			hook(resp, err)
   116  		}
   117  
   118  		// Don't need to wait when no retries left.
   119  		// Still run retry hooks even on last retry to keep compatibility.
   120  		if attempt == opts.maxRetries {
   121  			return err
   122  		}
   123  
   124  		waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
   125  		if err2 != nil {
   126  			if err == nil {
   127  				err = err2
   128  			}
   129  			return err
   130  		}
   131  
   132  		select {
   133  		case <-time.After(waitTime):
   134  		case <-ctx.Done():
   135  			return ctx.Err()
   136  		}
   137  	}
   138  
   139  	return err
   140  }
   141  
   142  func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
   143  	const maxInt = 1<<31 - 1 // max int for arch 386
   144  	if max < 0 {
   145  		max = maxInt
   146  	}
   147  	if resp == nil {
   148  		return jitterBackoff(min, max, attempt), nil
   149  	}
   150  
   151  	retryAfterFunc := resp.Request.client.RetryAfter
   152  
   153  	// Check for custom callback
   154  	if retryAfterFunc == nil {
   155  		return jitterBackoff(min, max, attempt), nil
   156  	}
   157  
   158  	result, err := retryAfterFunc(resp.Request.client, resp)
   159  	if err != nil {
   160  		return 0, err // i.e. 'API quota exceeded'
   161  	}
   162  	if result == 0 {
   163  		return jitterBackoff(min, max, attempt), nil
   164  	}
   165  	if result < 0 || max < result {
   166  		result = max
   167  	}
   168  	if result < min {
   169  		result = min
   170  	}
   171  	return result, nil
   172  }
   173  
   174  // Return capped exponential backoff with jitter
   175  // http://www.awsarchitectureblog.com/2015/03/backoff.html
   176  func jitterBackoff(min, max time.Duration, attempt int) time.Duration {
   177  	base := float64(min)
   178  	capLevel := float64(max)
   179  
   180  	temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
   181  	ri := time.Duration(temp / 2)
   182  	result := randDuration(ri)
   183  
   184  	if result < min {
   185  		result = min
   186  	}
   187  
   188  	return result
   189  }
   190  
   191  var (
   192  	rnd   = newRnd()
   193  	rndMu sync.Mutex
   194  )
   195  
   196  func randDuration(center time.Duration) time.Duration {
   197  	rndMu.Lock()
   198  	defer rndMu.Unlock()
   199  
   200  	ri := int64(center)
   201  	jitter := rnd.Int63n(ri)
   202  	return time.Duration(math.Abs(float64(ri + jitter)))
   203  }
   204  
   205  func newRnd() *rand.Rand {
   206  	seed := time.Now().UnixNano()
   207  	src := rand.NewSource(seed)
   208  	return rand.New(src)
   209  }