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 }