Dep ensure (#1803)
* vendor: don't vendor the context stuff We don't need to vendor this anymore as we moved to the std lib for these. * new stuff showing up with dep ensure * remove go-shlex
This commit is contained in:
parent
cffa1948ab
commit
1e471a353e
10377 changed files with 4225826 additions and 54911 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