plugin/metrics: Switch to using promhttp instead of deprecated Handler (#1312)
prometheus.Handler is deprecated according to the godoc for the package so instead we're using promhttp. Additionally, we are exposing the Registry that metrics is using so other plugins that are not inside of coredns can read the registry. Otherwise, if we kept using the Default one, there's no way to access that from outside of the coredns repo since it is vendored.
This commit is contained in:
parent
1919913c98
commit
671d170619
6728 changed files with 1994787 additions and 16 deletions
30
vendor/github.com/eapache/go-resiliency/batcher/README.md
generated
vendored
Normal file
30
vendor/github.com/eapache/go-resiliency/batcher/README.md
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
batcher
|
||||
=======
|
||||
|
||||
[](https://travis-ci.org/eapache/go-resiliency)
|
||||
[](https://godoc.org/github.com/eapache/go-resiliency/batcher)
|
||||
|
||||
The batching resiliency pattern for golang.
|
||||
|
||||
Creating a batcher takes two parameters:
|
||||
- the timeout to wait while collecting a batch
|
||||
- the function to run once a batch has been collected
|
||||
|
||||
You can also optionally set a prefilter to fail queries before they enter the
|
||||
batch.
|
||||
|
||||
```go
|
||||
b := batcher.New(10*time.Millisecond, func(params []interface{}) error {
|
||||
// do something with the batch of parameters
|
||||
return nil
|
||||
})
|
||||
|
||||
b.Prefilter(func(param interface{}) error {
|
||||
// do some sort of sanity check on the parameter, and return an error if it fails
|
||||
return nil
|
||||
})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go b.Run(i)
|
||||
}
|
||||
```
|
108
vendor/github.com/eapache/go-resiliency/batcher/batcher.go
generated
vendored
Normal file
108
vendor/github.com/eapache/go-resiliency/batcher/batcher.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Package batcher implements the batching resiliency pattern for Go.
|
||||
package batcher
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type work struct {
|
||||
param interface{}
|
||||
future chan error
|
||||
}
|
||||
|
||||
// Batcher implements the batching resiliency pattern
|
||||
type Batcher struct {
|
||||
timeout time.Duration
|
||||
prefilter func(interface{}) error
|
||||
|
||||
lock sync.Mutex
|
||||
submit chan *work
|
||||
doWork func([]interface{}) error
|
||||
}
|
||||
|
||||
// New constructs a new batcher that will batch all calls to Run that occur within
|
||||
// `timeout` time before calling doWork just once for the entire batch. The doWork
|
||||
// function must be safe to run concurrently with itself as this may occur, especially
|
||||
// when the timeout is small.
|
||||
func New(timeout time.Duration, doWork func([]interface{}) error) *Batcher {
|
||||
return &Batcher{
|
||||
timeout: timeout,
|
||||
doWork: doWork,
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the work function with the given parameter, possibly
|
||||
// including it in a batch with other calls to Run that occur within the
|
||||
// specified timeout. It is safe to call Run concurrently on the same batcher.
|
||||
func (b *Batcher) Run(param interface{}) error {
|
||||
if b.prefilter != nil {
|
||||
if err := b.prefilter(param); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if b.timeout == 0 {
|
||||
return b.doWork([]interface{}{param})
|
||||
}
|
||||
|
||||
w := &work{
|
||||
param: param,
|
||||
future: make(chan error, 1),
|
||||
}
|
||||
|
||||
b.submitWork(w)
|
||||
|
||||
return <-w.future
|
||||
}
|
||||
|
||||
// Prefilter specifies an optional function that can be used to run initial checks on parameters
|
||||
// passed to Run before being added to the batch. If the prefilter returns a non-nil error,
|
||||
// that error is returned immediately from Run and the batcher is not invoked. A prefilter
|
||||
// cannot safely be specified for a batcher if Run has already been invoked. The filter function
|
||||
// specified must be concurrency-safe.
|
||||
func (b *Batcher) Prefilter(filter func(interface{}) error) {
|
||||
b.prefilter = filter
|
||||
}
|
||||
|
||||
func (b *Batcher) submitWork(w *work) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if b.submit == nil {
|
||||
b.submit = make(chan *work, 4)
|
||||
go b.batch()
|
||||
}
|
||||
|
||||
b.submit <- w
|
||||
}
|
||||
|
||||
func (b *Batcher) batch() {
|
||||
var params []interface{}
|
||||
var futures []chan error
|
||||
input := b.submit
|
||||
|
||||
go b.timer()
|
||||
|
||||
for work := range input {
|
||||
params = append(params, work.param)
|
||||
futures = append(futures, work.future)
|
||||
}
|
||||
|
||||
ret := b.doWork(params)
|
||||
|
||||
for _, future := range futures {
|
||||
future <- ret
|
||||
close(future)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Batcher) timer() {
|
||||
time.Sleep(b.timeout)
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
close(b.submit)
|
||||
b.submit = nil
|
||||
}
|
123
vendor/github.com/eapache/go-resiliency/batcher/batcher_test.go
generated
vendored
Normal file
123
vendor/github.com/eapache/go-resiliency/batcher/batcher_test.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
package batcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errSomeError = errors.New("errSomeError")
|
||||
|
||||
func returnsError(params []interface{}) error {
|
||||
return errSomeError
|
||||
}
|
||||
|
||||
func returnsSuccess(params []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBatcherSuccess(t *testing.T) {
|
||||
b := New(10*time.Millisecond, returnsSuccess)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := b.Run(nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
b = New(0, returnsSuccess)
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := b.Run(nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatcherError(t *testing.T) {
|
||||
b := New(10*time.Millisecond, returnsError)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := b.Run(nil); err != errSomeError {
|
||||
t.Error(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestBatcherPrefilter(t *testing.T) {
|
||||
b := New(1*time.Millisecond, returnsSuccess)
|
||||
|
||||
b.Prefilter(func(param interface{}) error {
|
||||
if param == nil {
|
||||
return errSomeError
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := b.Run(nil); err != errSomeError {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := b.Run(1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatcherMultipleBatches(t *testing.T) {
|
||||
var iters uint32
|
||||
|
||||
b := New(10*time.Millisecond, func(params []interface{}) error {
|
||||
atomic.AddUint32(&iters, 1)
|
||||
return nil
|
||||
})
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for group := 0; group < 5; group++ {
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := b.Run(nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if iters != 5 {
|
||||
t.Error("Wrong number of iters:", iters)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleBatcher() {
|
||||
b := New(10*time.Millisecond, func(params []interface{}) error {
|
||||
// do something with the batch of parameters
|
||||
return nil
|
||||
})
|
||||
|
||||
b.Prefilter(func(param interface{}) error {
|
||||
// do some sort of sanity check on the parameter, and return an error if it fails
|
||||
return nil
|
||||
})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go b.Run(i)
|
||||
}
|
||||
}
|
26
vendor/github.com/eapache/go-resiliency/deadline/README.md
generated
vendored
Normal file
26
vendor/github.com/eapache/go-resiliency/deadline/README.md
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
deadline
|
||||
========
|
||||
|
||||
[](https://travis-ci.org/eapache/go-resiliency)
|
||||
[](https://godoc.org/github.com/eapache/go-resiliency/deadline)
|
||||
|
||||
The deadline/timeout resiliency pattern for golang.
|
||||
|
||||
Creating a deadline takes one parameter: how long to wait.
|
||||
|
||||
```go
|
||||
dl := deadline.New(1 * time.Second)
|
||||
|
||||
err := dl.Run(func(stopper <-chan struct{}) error {
|
||||
// do something possibly slow
|
||||
// check stopper function and give up if timed out
|
||||
return nil
|
||||
})
|
||||
|
||||
switch err {
|
||||
case deadline.ErrTimedOut:
|
||||
// execution took too long, oops
|
||||
default:
|
||||
// some other error
|
||||
}
|
||||
```
|
45
vendor/github.com/eapache/go-resiliency/deadline/deadline.go
generated
vendored
Normal file
45
vendor/github.com/eapache/go-resiliency/deadline/deadline.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Package deadline implements the deadline (also known as "timeout") resiliency pattern for Go.
|
||||
package deadline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrTimedOut is the error returned from Run when the deadline expires.
|
||||
var ErrTimedOut = errors.New("timed out waiting for function to finish")
|
||||
|
||||
// Deadline implements the deadline/timeout resiliency pattern.
|
||||
type Deadline struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// New constructs a new Deadline with the given timeout.
|
||||
func New(timeout time.Duration) *Deadline {
|
||||
return &Deadline{
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the given function, passing it a stopper channel. If the deadline passes before
|
||||
// the function finishes executing, Run returns ErrTimeOut to the caller and closes the stopper
|
||||
// channel so that the work function can attempt to exit gracefully. It does not (and cannot)
|
||||
// simply kill the running function, so if it doesn't respect the stopper channel then it may
|
||||
// keep running after the deadline passes. If the function finishes before the deadline, then
|
||||
// the return value of the function is returned from Run.
|
||||
func (d *Deadline) Run(work func(<-chan struct{}) error) error {
|
||||
result := make(chan error)
|
||||
stopper := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
result <- work(stopper)
|
||||
}()
|
||||
|
||||
select {
|
||||
case ret := <-result:
|
||||
return ret
|
||||
case <-time.After(d.timeout):
|
||||
close(stopper)
|
||||
return ErrTimedOut
|
||||
}
|
||||
}
|
65
vendor/github.com/eapache/go-resiliency/deadline/deadline_test.go
generated
vendored
Normal file
65
vendor/github.com/eapache/go-resiliency/deadline/deadline_test.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package deadline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func takesFiveMillis(stopper <-chan struct{}) error {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func takesTwentyMillis(stopper <-chan struct{}) error {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnsError(stopper <-chan struct{}) error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
func TestDeadline(t *testing.T) {
|
||||
dl := New(10 * time.Millisecond)
|
||||
|
||||
if err := dl.Run(takesFiveMillis); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := dl.Run(returnsError); err.Error() != "foo" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
err := dl.Run(func(stopper <-chan struct{}) error {
|
||||
<-stopper
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
if err != ErrTimedOut {
|
||||
t.Error(err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
func ExampleDeadline() {
|
||||
dl := New(1 * time.Second)
|
||||
|
||||
err := dl.Run(func(stopper <-chan struct{}) error {
|
||||
// do something possibly slow
|
||||
// check stopper function and give up if timed out
|
||||
return nil
|
||||
})
|
||||
|
||||
switch err {
|
||||
case ErrTimedOut:
|
||||
// execution took too long, oops
|
||||
default:
|
||||
// some other error
|
||||
}
|
||||
}
|
25
vendor/github.com/eapache/go-resiliency/retrier/README.md
generated
vendored
Normal file
25
vendor/github.com/eapache/go-resiliency/retrier/README.md
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
retrier
|
||||
=======
|
||||
|
||||
[](https://travis-ci.org/eapache/go-resiliency)
|
||||
[](https://godoc.org/github.com/eapache/go-resiliency/retrier)
|
||||
|
||||
The retriable resiliency pattern for golang.
|
||||
|
||||
Creating a retrier takes two parameters:
|
||||
- the times to back-off between retries (and implicitly the number of times to
|
||||
retry)
|
||||
- the classifier that determines which errors to retry
|
||||
|
||||
```go
|
||||
r := retrier.New(retrier.ConstantBackoff(3, 100*time.Millisecond), nil)
|
||||
|
||||
err := r.Run(func() error {
|
||||
// do some work
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// handle the case where the work failed three times
|
||||
}
|
||||
```
|
24
vendor/github.com/eapache/go-resiliency/retrier/backoffs.go
generated
vendored
Normal file
24
vendor/github.com/eapache/go-resiliency/retrier/backoffs.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package retrier
|
||||
|
||||
import "time"
|
||||
|
||||
// ConstantBackoff generates a simple back-off strategy of retrying 'n' times, and waiting 'amount' time after each one.
|
||||
func ConstantBackoff(n int, amount time.Duration) []time.Duration {
|
||||
ret := make([]time.Duration, n)
|
||||
for i := range ret {
|
||||
ret[i] = amount
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ExponentialBackoff generates a simple back-off strategy of retrying 'n' times, and doubling the amount of
|
||||
// time waited after each one.
|
||||
func ExponentialBackoff(n int, initialAmount time.Duration) []time.Duration {
|
||||
ret := make([]time.Duration, n)
|
||||
next := initialAmount
|
||||
for i := range ret {
|
||||
ret[i] = next
|
||||
next *= 2
|
||||
}
|
||||
return ret
|
||||
}
|
55
vendor/github.com/eapache/go-resiliency/retrier/backoffs_test.go
generated
vendored
Normal file
55
vendor/github.com/eapache/go-resiliency/retrier/backoffs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
package retrier
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConstantBackoff(t *testing.T) {
|
||||
b := ConstantBackoff(1, 10*time.Millisecond)
|
||||
if len(b) != 1 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
for i := range b {
|
||||
if b[i] != 10*time.Millisecond {
|
||||
t.Error("incorrect value at", i)
|
||||
}
|
||||
}
|
||||
|
||||
b = ConstantBackoff(10, 250*time.Hour)
|
||||
if len(b) != 10 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
for i := range b {
|
||||
if b[i] != 250*time.Hour {
|
||||
t.Error("incorrect value at", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExponentialBackoff(t *testing.T) {
|
||||
b := ExponentialBackoff(1, 10*time.Millisecond)
|
||||
if len(b) != 1 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
if b[0] != 10*time.Millisecond {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
|
||||
b = ExponentialBackoff(4, 1*time.Minute)
|
||||
if len(b) != 4 {
|
||||
t.Error("incorrect length")
|
||||
}
|
||||
if b[0] != 1*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[1] != 2*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[2] != 4*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
if b[3] != 8*time.Minute {
|
||||
t.Error("incorrect value")
|
||||
}
|
||||
}
|
66
vendor/github.com/eapache/go-resiliency/retrier/classifier.go
generated
vendored
Normal file
66
vendor/github.com/eapache/go-resiliency/retrier/classifier.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
package retrier
|
||||
|
||||
// Action is the type returned by a Classifier to indicate how the Retrier should proceed.
|
||||
type Action int
|
||||
|
||||
const (
|
||||
Succeed Action = iota // Succeed indicates the Retrier should treat this value as a success.
|
||||
Fail // Fail indicates the Retrier should treat this value as a hard failure and not retry.
|
||||
Retry // Retry indicates the Retrier should treat this value as a soft failure and retry.
|
||||
)
|
||||
|
||||
// Classifier is the interface implemented by anything that can classify Errors for a Retrier.
|
||||
type Classifier interface {
|
||||
Classify(error) Action
|
||||
}
|
||||
|
||||
// DefaultClassifier classifies errors in the simplest way possible. If
|
||||
// the error is nil, it returns Succeed, otherwise it returns Retry.
|
||||
type DefaultClassifier struct{}
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (c DefaultClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
return Retry
|
||||
}
|
||||
|
||||
// WhitelistClassifier classifies errors based on a whitelist. If the error is nil, it
|
||||
// returns Succeed; if the error is in the whitelist, it returns Retry; otherwise, it returns Fail.
|
||||
type WhitelistClassifier []error
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (list WhitelistClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
for _, pass := range list {
|
||||
if err == pass {
|
||||
return Retry
|
||||
}
|
||||
}
|
||||
|
||||
return Fail
|
||||
}
|
||||
|
||||
// BlacklistClassifier classifies errors based on a blacklist. If the error is nil, it
|
||||
// returns Succeed; if the error is in the blacklist, it returns Fail; otherwise, it returns Retry.
|
||||
type BlacklistClassifier []error
|
||||
|
||||
// Classify implements the Classifier interface.
|
||||
func (list BlacklistClassifier) Classify(err error) Action {
|
||||
if err == nil {
|
||||
return Succeed
|
||||
}
|
||||
|
||||
for _, pass := range list {
|
||||
if err == pass {
|
||||
return Fail
|
||||
}
|
||||
}
|
||||
|
||||
return Retry
|
||||
}
|
66
vendor/github.com/eapache/go-resiliency/retrier/classifier_test.go
generated
vendored
Normal file
66
vendor/github.com/eapache/go-resiliency/retrier/classifier_test.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
package retrier
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
errFoo = errors.New("FOO")
|
||||
errBar = errors.New("BAR")
|
||||
errBaz = errors.New("BAZ")
|
||||
)
|
||||
|
||||
func TestDefaultClassifier(t *testing.T) {
|
||||
c := DefaultClassifier{}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("default misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("default misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Retry {
|
||||
t.Error("default misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Retry {
|
||||
t.Error("default misclassified baz")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhitelistClassifier(t *testing.T) {
|
||||
c := WhitelistClassifier{errFoo, errBar}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("whitelist misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("whitelist misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Retry {
|
||||
t.Error("whitelist misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Fail {
|
||||
t.Error("whitelist misclassified baz")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlacklistClassifier(t *testing.T) {
|
||||
c := BlacklistClassifier{errBar}
|
||||
|
||||
if c.Classify(nil) != Succeed {
|
||||
t.Error("blacklist misclassified nil")
|
||||
}
|
||||
|
||||
if c.Classify(errFoo) != Retry {
|
||||
t.Error("blacklist misclassified foo")
|
||||
}
|
||||
if c.Classify(errBar) != Fail {
|
||||
t.Error("blacklist misclassified bar")
|
||||
}
|
||||
if c.Classify(errBaz) != Retry {
|
||||
t.Error("blacklist misclassified baz")
|
||||
}
|
||||
}
|
69
vendor/github.com/eapache/go-resiliency/retrier/retrier.go
generated
vendored
Normal file
69
vendor/github.com/eapache/go-resiliency/retrier/retrier.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Package retrier implements the "retriable" resiliency pattern for Go.
|
||||
package retrier
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
|
||||
// a certain number of times with an optional back-off between each retry.
|
||||
type Retrier struct {
|
||||
backoff []time.Duration
|
||||
class Classifier
|
||||
jitter float64
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
|
||||
// indicates how many times an action will be retried, and the value at each index indicates the amount of time
|
||||
// waited before each subsequent retry. The classifier is used to determine which errors should be retried and
|
||||
// which should cause the retrier to fail fast. The DefaultClassifier is used if nil is passed.
|
||||
func New(backoff []time.Duration, class Classifier) *Retrier {
|
||||
if class == nil {
|
||||
class = DefaultClassifier{}
|
||||
}
|
||||
|
||||
return &Retrier{
|
||||
backoff: backoff,
|
||||
class: class,
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the given work function, then classifies its return value based on the classifier used
|
||||
// to construct the Retrier. If the result is Succeed or Fail, the return value of the work function is
|
||||
// returned to the caller. If the result is Retry, then Run sleeps according to the its backoff policy
|
||||
// before retrying. If the total number of retries is exceeded then the return value of the work function
|
||||
// is returned to the caller regardless.
|
||||
func (r *Retrier) Run(work func() error) error {
|
||||
retries := 0
|
||||
for {
|
||||
ret := work()
|
||||
|
||||
switch r.class.Classify(ret) {
|
||||
case Succeed, Fail:
|
||||
return ret
|
||||
case Retry:
|
||||
if retries >= len(r.backoff) {
|
||||
return ret
|
||||
}
|
||||
time.Sleep(r.calcSleep(retries))
|
||||
retries++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retrier) calcSleep(i int) time.Duration {
|
||||
// take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount
|
||||
return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i]))
|
||||
}
|
||||
|
||||
// SetJitter sets the amount of jitter on each back-off to a factor between 0.0 and 1.0 (values outside this range
|
||||
// are silently ignored). When a retry occurs, the back-off is adjusted by a random amount up to this value.
|
||||
func (r *Retrier) SetJitter(jit float64) {
|
||||
if jit < 0 || jit > 1 {
|
||||
return
|
||||
}
|
||||
r.jitter = jit
|
||||
}
|
129
vendor/github.com/eapache/go-resiliency/retrier/retrier_test.go
generated
vendored
Normal file
129
vendor/github.com/eapache/go-resiliency/retrier/retrier_test.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
package retrier
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var i int
|
||||
|
||||
func genWork(returns []error) func() error {
|
||||
i = 0
|
||||
return func() error {
|
||||
i++
|
||||
if i > len(returns) {
|
||||
return nil
|
||||
}
|
||||
return returns[i-1]
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrier(t *testing.T) {
|
||||
r := New([]time.Duration{0, 10 * time.Millisecond}, WhitelistClassifier{errFoo})
|
||||
|
||||
err := r.Run(genWork([]error{errFoo, errFoo}))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 3 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
err = r.Run(genWork([]error{errFoo, errBar}))
|
||||
if err != errBar {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 2 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
err = r.Run(genWork([]error{errBar, errBaz}))
|
||||
if err != errBar {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrierNone(t *testing.T) {
|
||||
r := New(nil, nil)
|
||||
|
||||
i = 0
|
||||
err := r.Run(func() error {
|
||||
i++
|
||||
return errFoo
|
||||
})
|
||||
if err != errFoo {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
|
||||
i = 0
|
||||
err = r.Run(func() error {
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("run wrong number of times")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrierJitter(t *testing.T) {
|
||||
r := New([]time.Duration{0, 10 * time.Millisecond, 4 * time.Hour}, nil)
|
||||
|
||||
if r.calcSleep(0) != 0 {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
if r.calcSleep(1) != 10*time.Millisecond {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
if r.calcSleep(2) != 4*time.Hour {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
r.SetJitter(0.25)
|
||||
for i := 0; i < 20; i++ {
|
||||
if r.calcSleep(0) != 0 {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
slp := r.calcSleep(1)
|
||||
if slp < 7500*time.Microsecond || slp > 12500*time.Microsecond {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
|
||||
slp = r.calcSleep(2)
|
||||
if slp < 3*time.Hour || slp > 5*time.Hour {
|
||||
t.Error("Incorrect sleep calculated")
|
||||
}
|
||||
}
|
||||
|
||||
r.SetJitter(-1)
|
||||
if r.jitter != 0.25 {
|
||||
t.Error("Invalid jitter value accepted")
|
||||
}
|
||||
|
||||
r.SetJitter(2)
|
||||
if r.jitter != 0.25 {
|
||||
t.Error("Invalid jitter value accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleRetrier() {
|
||||
r := New(ConstantBackoff(3, 100*time.Millisecond), nil)
|
||||
|
||||
err := r.Run(func() error {
|
||||
// do some work
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// handle the case where the work failed three times
|
||||
}
|
||||
}
|
21
vendor/github.com/eapache/go-resiliency/semaphore/README.md
generated
vendored
Normal file
21
vendor/github.com/eapache/go-resiliency/semaphore/README.md
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
semaphore
|
||||
=========
|
||||
|
||||
[](https://travis-ci.org/eapache/go-resiliency)
|
||||
[](https://godoc.org/github.com/eapache/go-resiliency/semaphore)
|
||||
|
||||
The semaphore resiliency pattern for golang.
|
||||
|
||||
Creating a semaphore takes two parameters:
|
||||
- ticket count (how many tickets to give out at once)
|
||||
- timeout (how long to wait for a ticket if none are currently available)
|
||||
|
||||
```go
|
||||
sem := semaphore.New(3, 1*time.Second)
|
||||
|
||||
if err := sem.Acquire(); err != nil {
|
||||
// could not acquire semaphore
|
||||
return err
|
||||
}
|
||||
defer sem.Release()
|
||||
```
|
45
vendor/github.com/eapache/go-resiliency/semaphore/semaphore.go
generated
vendored
Normal file
45
vendor/github.com/eapache/go-resiliency/semaphore/semaphore.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Package semaphore implements the semaphore resiliency pattern for Go.
|
||||
package semaphore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNoTickets is the error returned by Acquire when it could not acquire
|
||||
// a ticket from the semaphore within the configured timeout.
|
||||
var ErrNoTickets = errors.New("could not aquire semaphore ticket")
|
||||
|
||||
// Semaphore implements the semaphore resiliency pattern
|
||||
type Semaphore struct {
|
||||
sem chan struct{}
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// New constructs a new Semaphore with the given ticket-count
|
||||
// and timeout.
|
||||
func New(tickets int, timeout time.Duration) *Semaphore {
|
||||
return &Semaphore{
|
||||
sem: make(chan struct{}, tickets),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire tries to acquire a ticket from the semaphore. If it can, it returns nil.
|
||||
// If it cannot after "timeout" amount of time, it returns ErrNoTickets. It is
|
||||
// safe to call Acquire concurrently on a single Semaphore.
|
||||
func (s *Semaphore) Acquire() error {
|
||||
select {
|
||||
case s.sem <- struct{}{}:
|
||||
return nil
|
||||
case <-time.After(s.timeout):
|
||||
return ErrNoTickets
|
||||
}
|
||||
}
|
||||
|
||||
// Release releases an acquired ticket back to the semaphore. It is safe to call
|
||||
// Release concurrently on a single Semaphore. It is an error to call Release on
|
||||
// a Semaphore from which you have not first acquired a ticket.
|
||||
func (s *Semaphore) Release() {
|
||||
<-s.sem
|
||||
}
|
61
vendor/github.com/eapache/go-resiliency/semaphore/semaphore_test.go
generated
vendored
Normal file
61
vendor/github.com/eapache/go-resiliency/semaphore/semaphore_test.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
package semaphore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSemaphoreAcquireRelease(t *testing.T) {
|
||||
sem := New(3, 1*time.Second)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := sem.Acquire(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := sem.Acquire(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := sem.Acquire(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
sem.Release()
|
||||
sem.Release()
|
||||
sem.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBlockTimeout(t *testing.T) {
|
||||
sem := New(1, 200*time.Millisecond)
|
||||
|
||||
if err := sem.Acquire(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if err := sem.Acquire(); err != ErrNoTickets {
|
||||
t.Error(err)
|
||||
}
|
||||
if start.Add(200 * time.Millisecond).After(time.Now()) {
|
||||
t.Error("semaphore did not wait long enough")
|
||||
}
|
||||
|
||||
sem.Release()
|
||||
if err := sem.Acquire(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSemaphore() {
|
||||
sem := New(3, 1*time.Second)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
if err := sem.Acquire(); err != nil {
|
||||
return //could not acquire semaphore
|
||||
}
|
||||
defer sem.Release()
|
||||
|
||||
// do something semaphore-guarded
|
||||
}()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue