diff --git a/util/hook/hook.go b/util/hook/hook.go new file mode 100644 index 00000000..62079d73 --- /dev/null +++ b/util/hook/hook.go @@ -0,0 +1,111 @@ +// 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) + } + +}