github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/pacer/pacer.go (about)

     1  // Package pacer makes pacing and retrying API calls easy
     2  package pacer
     3  
     4  import (
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/rclone/rclone/lib/errors"
     9  )
    10  
    11  // State represents the public Pacer state that will be passed to the
    12  // configured Calculator
    13  type State struct {
    14  	SleepTime          time.Duration // current time to sleep before adding the pacer token back
    15  	ConsecutiveRetries int           // number of consecutive retries, will be 0 when the last invoker call returned false
    16  	LastError          error         // the error returned by the last invoker call or nil
    17  }
    18  
    19  // Calculator is a generic calculation function for a Pacer.
    20  type Calculator interface {
    21  	// Calculate takes the current Pacer state and returns the sleep time after which
    22  	// the next Pacer call will be done.
    23  	Calculate(state State) time.Duration
    24  }
    25  
    26  // Pacer is the primary type of the pacer package. It allows to retry calls
    27  // with a configurable delay in between.
    28  type Pacer struct {
    29  	pacerOptions
    30  	mu         sync.Mutex    // Protecting read/writes
    31  	pacer      chan struct{} // To pace the operations
    32  	connTokens chan struct{} // Connection tokens
    33  	state      State
    34  }
    35  type pacerOptions struct {
    36  	maxConnections int         // Maximum number of concurrent connections
    37  	retries        int         // Max number of retries
    38  	calculator     Calculator  // switchable pacing algorithm - call with mu held
    39  	invoker        InvokerFunc // wrapper function used to invoke the target function
    40  }
    41  
    42  // InvokerFunc is the signature of the wrapper function used to invoke the
    43  // target function in Pacer.
    44  type InvokerFunc func(try, tries int, f Paced) (bool, error)
    45  
    46  // Option can be used in New to configure the Pacer.
    47  type Option func(*pacerOptions)
    48  
    49  // CalculatorOption sets a Calculator for the new Pacer.
    50  func CalculatorOption(c Calculator) Option {
    51  	return func(p *pacerOptions) { p.calculator = c }
    52  }
    53  
    54  // RetriesOption sets the retries number for the new Pacer.
    55  func RetriesOption(retries int) Option {
    56  	return func(p *pacerOptions) { p.retries = retries }
    57  }
    58  
    59  // MaxConnectionsOption sets the maximum connections number for the new Pacer.
    60  func MaxConnectionsOption(maxConnections int) Option {
    61  	return func(p *pacerOptions) { p.maxConnections = maxConnections }
    62  }
    63  
    64  // InvokerOption sets an InvokerFunc for the new Pacer.
    65  func InvokerOption(invoker InvokerFunc) Option {
    66  	return func(p *pacerOptions) { p.invoker = invoker }
    67  }
    68  
    69  // Paced is a function which is called by the Call and CallNoRetry
    70  // methods.  It should return a boolean, true if it would like to be
    71  // retried, and an error.  This error may be returned or returned
    72  // wrapped in a RetryError.
    73  type Paced func() (bool, error)
    74  
    75  // New returns a Pacer with sensible defaults.
    76  func New(options ...Option) *Pacer {
    77  	opts := pacerOptions{
    78  		maxConnections: 10,
    79  		retries:        3,
    80  	}
    81  	for _, o := range options {
    82  		o(&opts)
    83  	}
    84  	p := &Pacer{
    85  		pacerOptions: opts,
    86  		pacer:        make(chan struct{}, 1),
    87  	}
    88  	if p.calculator == nil {
    89  		p.SetCalculator(nil)
    90  	}
    91  	p.state.SleepTime = p.calculator.Calculate(p.state)
    92  	if p.invoker == nil {
    93  		p.invoker = invoke
    94  	}
    95  	p.SetMaxConnections(p.maxConnections)
    96  
    97  	// Put the first pacing token in
    98  	p.pacer <- struct{}{}
    99  
   100  	return p
   101  }
   102  
   103  // SetMaxConnections sets the maximum number of concurrent connections.
   104  // Setting the value to 0 will allow unlimited number of connections.
   105  // Should not be changed once you have started calling the pacer.
   106  // By default this will be set to fs.Config.Checkers.
   107  func (p *Pacer) SetMaxConnections(n int) {
   108  	p.mu.Lock()
   109  	defer p.mu.Unlock()
   110  	p.maxConnections = n
   111  	if n <= 0 {
   112  		p.connTokens = nil
   113  	} else {
   114  		p.connTokens = make(chan struct{}, n)
   115  		for i := 0; i < n; i++ {
   116  			p.connTokens <- struct{}{}
   117  		}
   118  	}
   119  }
   120  
   121  // SetRetries sets the max number of retries for Call
   122  func (p *Pacer) SetRetries(retries int) {
   123  	p.mu.Lock()
   124  	defer p.mu.Unlock()
   125  	p.retries = retries
   126  }
   127  
   128  // SetCalculator sets the pacing algorithm. Don't modify the Calculator object
   129  // afterwards, use the ModifyCalculator method when needed.
   130  //
   131  // It will choose the default algorithm if nil is passed in.
   132  func (p *Pacer) SetCalculator(c Calculator) {
   133  	p.mu.Lock()
   134  	defer p.mu.Unlock()
   135  	if c == nil {
   136  		c = NewDefault()
   137  	}
   138  	p.calculator = c
   139  }
   140  
   141  // ModifyCalculator calls the given function with the currently configured
   142  // Calculator and the Pacer lock held.
   143  func (p *Pacer) ModifyCalculator(f func(Calculator)) {
   144  	p.mu.Lock()
   145  	f(p.calculator)
   146  	p.mu.Unlock()
   147  }
   148  
   149  // Start a call to the API
   150  //
   151  // This must be called as a pair with endCall
   152  //
   153  // This waits for the pacer token
   154  func (p *Pacer) beginCall() {
   155  	// pacer starts with a token in and whenever we take one out
   156  	// XXX ms later we put another in.  We could do this with a
   157  	// Ticker more accurately, but then we'd have to work out how
   158  	// not to run it when it wasn't needed
   159  	<-p.pacer
   160  	if p.maxConnections > 0 {
   161  		<-p.connTokens
   162  	}
   163  
   164  	p.mu.Lock()
   165  	// Restart the timer
   166  	go func(t time.Duration) {
   167  		time.Sleep(t)
   168  		p.pacer <- struct{}{}
   169  	}(p.state.SleepTime)
   170  	p.mu.Unlock()
   171  }
   172  
   173  // endCall implements the pacing algorithm
   174  //
   175  // This should calculate a new sleepTime.  It takes a boolean as to
   176  // whether the operation should be retried or not.
   177  func (p *Pacer) endCall(retry bool, err error) {
   178  	if p.maxConnections > 0 {
   179  		p.connTokens <- struct{}{}
   180  	}
   181  	p.mu.Lock()
   182  	if retry {
   183  		p.state.ConsecutiveRetries++
   184  	} else {
   185  		p.state.ConsecutiveRetries = 0
   186  	}
   187  	p.state.LastError = err
   188  	p.state.SleepTime = p.calculator.Calculate(p.state)
   189  	p.mu.Unlock()
   190  }
   191  
   192  // call implements Call but with settable retries
   193  func (p *Pacer) call(fn Paced, retries int) (err error) {
   194  	var retry bool
   195  	for i := 1; i <= retries; i++ {
   196  		p.beginCall()
   197  		retry, err = p.invoker(i, retries, fn)
   198  		p.endCall(retry, err)
   199  		if !retry {
   200  			break
   201  		}
   202  	}
   203  	return err
   204  }
   205  
   206  // Call paces the remote operations to not exceed the limits and retry
   207  // on rate limit exceeded
   208  //
   209  // This calls fn, expecting it to return a retry flag and an
   210  // error. This error may be returned wrapped in a RetryError if the
   211  // number of retries is exceeded.
   212  func (p *Pacer) Call(fn Paced) (err error) {
   213  	p.mu.Lock()
   214  	retries := p.retries
   215  	p.mu.Unlock()
   216  	return p.call(fn, retries)
   217  }
   218  
   219  // CallNoRetry paces the remote operations to not exceed the limits
   220  // and return a retry error on rate limit exceeded
   221  //
   222  // This calls fn and wraps the output in a RetryError if it would like
   223  // it to be retried
   224  func (p *Pacer) CallNoRetry(fn Paced) error {
   225  	return p.call(fn, 1)
   226  }
   227  
   228  func invoke(try, tries int, f Paced) (bool, error) {
   229  	return f()
   230  }
   231  
   232  type retryAfterError struct {
   233  	error
   234  	retryAfter time.Duration
   235  }
   236  
   237  func (r *retryAfterError) Error() string {
   238  	return r.error.Error()
   239  }
   240  
   241  func (r *retryAfterError) Cause() error {
   242  	return r.error
   243  }
   244  
   245  // RetryAfterError returns a wrapped error that can be used by Calculator implementations
   246  func RetryAfterError(err error, retryAfter time.Duration) error {
   247  	return &retryAfterError{
   248  		error:      err,
   249  		retryAfter: retryAfter,
   250  	}
   251  }
   252  
   253  // IsRetryAfter returns true if the error or any of it's Cause's is an error
   254  // returned by RetryAfterError. It also returns the associated Duration if possible.
   255  func IsRetryAfter(err error) (retryAfter time.Duration, isRetryAfter bool) {
   256  	errors.Walk(err, func(err error) bool {
   257  		if r, ok := err.(*retryAfterError); ok {
   258  			retryAfter, isRetryAfter = r.retryAfter, true
   259  			return true
   260  		}
   261  		return false
   262  	})
   263  	return
   264  }