...

Source file src/github.com/go-resty/resty/v2/retry.go

Documentation: github.com/go-resty/resty/v2

     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(value int) Option {
    51  	return func(o *Options) {
    52  		o.maxRetries = value
    53  	}
    54  }
    55  
    56  // WaitTime sets the default wait time to sleep between requests
    57  func WaitTime(value time.Duration) Option {
    58  	return func(o *Options) {
    59  		o.waitTime = value
    60  	}
    61  }
    62  
    63  // MaxWaitTime sets the max wait time to sleep between requests
    64  func MaxWaitTime(value time.Duration) Option {
    65  	return func(o *Options) {
    66  		o.maxWaitTime = value
    67  	}
    68  }
    69  
    70  // RetryConditions sets the conditions that will be checked for retry.
    71  func RetryConditions(conditions []RetryConditionFunc) Option {
    72  	return func(o *Options) {
    73  		o.retryConditions = conditions
    74  	}
    75  }
    76  
    77  // RetryHooks sets the hooks that will be executed after each retry
    78  func RetryHooks(hooks []OnRetryFunc) Option {
    79  	return func(o *Options) {
    80  		o.retryHooks = hooks
    81  	}
    82  }
    83  
    84  // Backoff retries with increasing timeout duration up until X amount of retries
    85  // (Default is 3 attempts, Override with option Retries(n))
    86  func Backoff(operation func() (*Response, error), options ...Option) error {
    87  	// Defaults
    88  	opts := Options{
    89  		maxRetries:      defaultMaxRetries,
    90  		waitTime:        defaultWaitTime,
    91  		maxWaitTime:     defaultMaxWaitTime,
    92  		retryConditions: []RetryConditionFunc{},
    93  	}
    94  
    95  	for _, o := range options {
    96  		o(&opts)
    97  	}
    98  
    99  	var (
   100  		resp *Response
   101  		err  error
   102  	)
   103  
   104  	for attempt := 0; attempt <= opts.maxRetries; attempt++ {
   105  		resp, err = operation()
   106  		ctx := context.Background()
   107  		if resp != nil && resp.Request.ctx != nil {
   108  			ctx = resp.Request.ctx
   109  		}
   110  		if ctx.Err() != nil {
   111  			return err
   112  		}
   113  
   114  		err1 := unwrapNoRetryErr(err)           // raw error, it used for return users callback.
   115  		needsRetry := err != nil && err == err1 // retry on a few operation errors by default
   116  
   117  		for _, condition := range opts.retryConditions {
   118  			needsRetry = condition(resp, err1)
   119  			if needsRetry {
   120  				break
   121  			}
   122  		}
   123  
   124  		if !needsRetry {
   125  			return err
   126  		}
   127  
   128  		for _, hook := range opts.retryHooks {
   129  			hook(resp, err)
   130  		}
   131  
   132  		// Don't need to wait when no retries left.
   133  		// Still run retry hooks even on last retry to keep compatibility.
   134  		if attempt == opts.maxRetries {
   135  			return err
   136  		}
   137  
   138  		waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
   139  		if err2 != nil {
   140  			if err == nil {
   141  				err = err2
   142  			}
   143  			return err
   144  		}
   145  
   146  		select {
   147  		case <-time.After(waitTime):
   148  		case <-ctx.Done():
   149  			return ctx.Err()
   150  		}
   151  	}
   152  
   153  	return err
   154  }
   155  
   156  func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
   157  	const maxInt = 1<<31 - 1 // max int for arch 386
   158  	if max < 0 {
   159  		max = maxInt
   160  	}
   161  	if resp == nil {
   162  		return jitterBackoff(min, max, attempt), nil
   163  	}
   164  
   165  	retryAfterFunc := resp.Request.client.RetryAfter
   166  
   167  	// Check for custom callback
   168  	if retryAfterFunc == nil {
   169  		return jitterBackoff(min, max, attempt), nil
   170  	}
   171  
   172  	result, err := retryAfterFunc(resp.Request.client, resp)
   173  	if err != nil {
   174  		return 0, err // i.e. 'API quota exceeded'
   175  	}
   176  	if result == 0 {
   177  		return jitterBackoff(min, max, attempt), nil
   178  	}
   179  	if result < 0 || max < result {
   180  		result = max
   181  	}
   182  	if result < min {
   183  		result = min
   184  	}
   185  	return result, nil
   186  }
   187  
   188  // Return capped exponential backoff with jitter
   189  // http://www.awsarchitectureblog.com/2015/03/backoff.html
   190  func jitterBackoff(min, max time.Duration, attempt int) time.Duration {
   191  	base := float64(min)
   192  	capLevel := float64(max)
   193  
   194  	temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
   195  	ri := time.Duration(temp / 2)
   196  	result := randDuration(ri)
   197  
   198  	if result < min {
   199  		result = min
   200  	}
   201  
   202  	return result
   203  }
   204  
   205  var rnd = newRnd()
   206  var rndMu sync.Mutex
   207  
   208  func randDuration(center time.Duration) time.Duration {
   209  	rndMu.Lock()
   210  	defer rndMu.Unlock()
   211  
   212  	var ri = int64(center)
   213  	var jitter = rnd.Int63n(ri)
   214  	return time.Duration(math.Abs(float64(ri + jitter)))
   215  }
   216  
   217  func newRnd() *rand.Rand {
   218  	var seed = time.Now().UnixNano()
   219  	var src = rand.NewSource(seed)
   220  	return rand.New(src)
   221  }
   222  

View as plain text