surrealpatch/util/hook/hook.go
Tobie Morgan Hitchcock 44591abfe5 Add ‘hook’ package for retryable functions
The hook package enables static and backoff retryable functions. This package can be used for calling remote webhooks concurrently in separate goroutines.
2017-11-16 19:54:55 +00:00

111 lines
2.3 KiB
Go

// 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)
}
}