// Copyright © 2016 Abcum Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hook import ( "context" "time" ) const ( Static Kind = iota Backoff ) // Kind specifies the policy type. type Kind int // Policy represents a retryable function policy. type Policy struct { // Type is the policy type Type Kind // Attempts to retry Retry int // Factor is the backoff rate Factor int // Sleep is the initial duration to wait before retrying Sleep time.Duration } // New creates a new static retryable policy, which retries // after the duration of 'sleep', until the number of retries // has been reached. func NewStatic(retry int, sleep time.Duration) *Policy { return &Policy{ Type: Static, Retry: retry, Sleep: sleep, } } // New creates a new backoff retryable policy, which increases // the delay between subsequent retries by the secified factor, // until the number of retries has been reached. func NewBackoff(retry, factor int, sleep time.Duration) *Policy { return &Policy{ Type: Backoff, Retry: retry, Sleep: sleep, Factor: factor, } } // Run executes a function until: // 1. A nil error is returned, // 2. The max number of retries has been reached, // 3. The specified context has been cancelled or timedout. func (p *Policy) Run(ctx context.Context, fnc func() error) error { c := make(chan error, 1) go func() { c <- p.run(ctx, fnc) }() select { case <-ctx.Done(): return ctx.Err() case err := <-c: return err } } func (p *Policy) run(ctx context.Context, fnc func() error) error { select { case <-ctx.Done(): return ctx.Err() default: if err := fnc(); err != nil { if p.Retry > 0 { p.sleep() p.Retry = p.Retry - 1 return p.run(ctx, fnc) } } } return nil } func (p *Policy) sleep() { time.Sleep(p.Sleep) if p.Type == Backoff { p.Sleep = p.Sleep * time.Duration(p.Factor) } }