Updates vendors
This commit is contained in:
parent
e31aa3c746
commit
745c3a46c1
|
@ -0,0 +1,4 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- tip
|
|
@ -0,0 +1,6 @@
|
|||
ISC license
|
||||
Copyright (c) 2014, Frank Rosquin
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,94 @@
|
|||
# structhash [![GoDoc](https://godoc.org/github.com/cnf/structhash?status.svg)](https://godoc.org/github.com/cnf/structhash) [![Build Status](https://travis-ci.org/cnf/structhash.svg?branch=master)](https://travis-ci.org/cnf/structhash)
|
||||
|
||||
structhash is a Go library for generating hash strings of arbitrary data structures.
|
||||
|
||||
## Features
|
||||
|
||||
* fields may be ignored or renamed (like in `json.Marshal`, but using different struct tag)
|
||||
* fields may be versioned
|
||||
* fields order in struct doesn't matter (unlike `json.Marshal`)
|
||||
* nil values are treated equally to zero values
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/cnf/structhash
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/cnf/structhash).
|
||||
|
||||
## Quick start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"github.com/cnf/structhash"
|
||||
)
|
||||
|
||||
type S struct {
|
||||
Str string
|
||||
Num int
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := S{"hello", 123}
|
||||
|
||||
hash, err := structhash.Hash(s, 1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(hash)
|
||||
// Prints: v1_41011bfa1a996db6d0b1075981f5aa8f
|
||||
|
||||
fmt.Println(structhash.Version(hash))
|
||||
// Prints: 1
|
||||
|
||||
fmt.Printf("%x\n", structhash.Md5(s, 1))
|
||||
// Prints: 41011bfa1a996db6d0b1075981f5aa8f
|
||||
|
||||
fmt.Printf("%x\n", structhash.Sha1(s, 1))
|
||||
// Prints: 5ff72df7212ce8c55838fb3ec6ad0c019881a772
|
||||
|
||||
fmt.Printf("%x\n", md5.Sum(structhash.Dump(s, 1)))
|
||||
// Prints: 41011bfa1a996db6d0b1075981f5aa8f
|
||||
|
||||
fmt.Printf("%x\n", sha1.Sum(structhash.Dump(s, 1)))
|
||||
// Prints: 5ff72df7212ce8c55838fb3ec6ad0c019881a772
|
||||
}
|
||||
```
|
||||
|
||||
## Struct tags
|
||||
|
||||
structhash supports struct tags in the following forms:
|
||||
|
||||
* `hash:"-"`, or
|
||||
* `hash:"name:{string} version:{number} lastversion:{number}"`
|
||||
|
||||
All fields are optional and may be ommitted. Their semantics is:
|
||||
|
||||
* `-` - ignore field
|
||||
* `name:{string}` - rename field (may be useful when you want to rename field but keep hashes unchanged for backward compatibility)
|
||||
* `version:{number}` - ignore field if version passed to structhash is smaller than given number
|
||||
* `lastversion:{number}` - ignore field if version passed to structhash is greater than given number
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
Ignored string `hash:"-"`
|
||||
Renamed string `hash:"name:OldName version:1"`
|
||||
Legacy string `hash:"version:1 lastversion:2"`
|
||||
}
|
||||
```
|
||||
|
||||
## Nil values
|
||||
|
||||
When hash is calculated, nil pointers, nil slices, and nil maps are treated equally to zero values of corresponding type. E.g., nil pointer to string is equivalent to empty string, and nil slice is equivalent to empty slice.
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package structhash creates hash strings from arbitrary go data structures.
|
||||
*/
|
||||
package structhash
|
|
@ -0,0 +1,240 @@
|
|||
package structhash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version returns the version of the supplied hash as an integer
|
||||
// or -1 on failure
|
||||
func Version(h string) int {
|
||||
if h == "" {
|
||||
return -1
|
||||
}
|
||||
if h[0] != 'v' {
|
||||
return -1
|
||||
}
|
||||
if spos := strings.IndexRune(h[1:], '_'); spos >= 0 {
|
||||
n, e := strconv.Atoi(h[1 : spos+1])
|
||||
if e != nil {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Hash takes a data structure and returns a hash string of that data structure
|
||||
// at the version asked.
|
||||
//
|
||||
// This function uses md5 hashing function and default formatter. See also Dump()
|
||||
// function.
|
||||
func Hash(c interface{}, version int) (string, error) {
|
||||
return fmt.Sprintf("v%d_%x", version, Md5(c, version)), nil
|
||||
}
|
||||
|
||||
// Dump takes a data structure and returns its byte representation. This can be
|
||||
// useful if you need to use your own hashing function or formatter.
|
||||
func Dump(c interface{}, version int) []byte {
|
||||
return serialize(c, version)
|
||||
}
|
||||
|
||||
// Md5 takes a data structure and returns its md5 hash.
|
||||
// This is a shorthand for md5.Sum(Dump(c, version)).
|
||||
func Md5(c interface{}, version int) []byte {
|
||||
sum := md5.Sum(Dump(c, version))
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
// Sha1 takes a data structure and returns its sha1 hash.
|
||||
// This is a shorthand for sha1.Sum(Dump(c, version)).
|
||||
func Sha1(c interface{}, version int) []byte {
|
||||
sum := sha1.Sum(Dump(c, version))
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
type item struct {
|
||||
name string
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
type itemSorter []item
|
||||
|
||||
func (s itemSorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s itemSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s itemSorter) Less(i, j int) bool {
|
||||
return s[i].name < s[j].name
|
||||
}
|
||||
|
||||
type structFieldFilter func(reflect.StructField) (string, bool)
|
||||
|
||||
func writeValue(buf *bytes.Buffer, val reflect.Value, fltr structFieldFilter) {
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(val.String())
|
||||
buf.WriteByte('"')
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
buf.WriteString(strconv.FormatInt(val.Int(), 10))
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
buf.WriteString(strconv.FormatUint(val.Uint(), 10))
|
||||
case reflect.Bool:
|
||||
if val.Bool() {
|
||||
buf.WriteByte('t')
|
||||
} else {
|
||||
buf.WriteByte('f')
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if !val.IsNil() || val.Type().Elem().Kind() == reflect.Struct {
|
||||
writeValue(buf, reflect.Indirect(val), fltr)
|
||||
} else {
|
||||
writeValue(buf, reflect.Zero(val.Type().Elem()), fltr)
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
buf.WriteByte('[')
|
||||
len := val.Len()
|
||||
for i := 0; i < len; i++ {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
writeValue(buf, val.Index(i), fltr)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
case reflect.Map:
|
||||
mk := val.MapKeys()
|
||||
items := make([]item, len(mk), len(mk))
|
||||
// Get all values
|
||||
for i, _ := range items {
|
||||
items[i].name = formatValue(mk[i], fltr)
|
||||
items[i].value = val.MapIndex(mk[i])
|
||||
}
|
||||
|
||||
// Sort values by key
|
||||
sort.Sort(itemSorter(items))
|
||||
|
||||
buf.WriteByte('[')
|
||||
for i, _ := range items {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(items[i].name)
|
||||
buf.WriteByte(':')
|
||||
writeValue(buf, items[i].value, fltr)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
case reflect.Struct:
|
||||
vtype := val.Type()
|
||||
flen := vtype.NumField()
|
||||
items := make([]item, 0, flen)
|
||||
// Get all fields
|
||||
for i := 0; i < flen; i++ {
|
||||
field := vtype.Field(i)
|
||||
it := item{field.Name, val.Field(i)}
|
||||
if fltr != nil {
|
||||
if name, ok := fltr(field); ok {
|
||||
it.name = name
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
items = append(items, it)
|
||||
}
|
||||
// Sort fields by name
|
||||
sort.Sort(itemSorter(items))
|
||||
|
||||
buf.WriteByte('{')
|
||||
for i, _ := range items {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(items[i].name)
|
||||
buf.WriteByte(':')
|
||||
writeValue(buf, items[i].value, fltr)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
default:
|
||||
buf.WriteString(val.String())
|
||||
}
|
||||
}
|
||||
|
||||
func formatValue(val reflect.Value, fltr structFieldFilter) string {
|
||||
if val.Kind() == reflect.String {
|
||||
return "\"" + val.Interface().(string) + "\""
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
writeValue(&buf, val, fltr)
|
||||
|
||||
return string(buf.Bytes())
|
||||
}
|
||||
|
||||
func filterField(f reflect.StructField, version int) (string, bool) {
|
||||
var err error
|
||||
name := f.Name
|
||||
ver := 0
|
||||
lastver := -1
|
||||
if str := f.Tag.Get("hash"); str != "" {
|
||||
if str == "-" {
|
||||
return "", false
|
||||
}
|
||||
for _, tag := range strings.Split(str, " ") {
|
||||
args := strings.Split(strings.TrimSpace(tag), ":")
|
||||
if len(args) != 2 {
|
||||
return "", false
|
||||
}
|
||||
switch args[0] {
|
||||
case "name":
|
||||
name = args[1]
|
||||
case "version":
|
||||
if ver, err = strconv.Atoi(args[1]); err != nil {
|
||||
return "", false
|
||||
}
|
||||
case "lastversion":
|
||||
if lastver, err = strconv.Atoi(args[1]); err != nil {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if str := f.Tag.Get("lastversion"); str != "" {
|
||||
if lastver, err = strconv.Atoi(str); err != nil {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
if str := f.Tag.Get("version"); str != "" {
|
||||
if ver, err = strconv.Atoi(str); err != nil {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
if lastver != -1 && lastver < version {
|
||||
return "", false
|
||||
}
|
||||
if ver > version {
|
||||
return "", false
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
|
||||
func serialize(object interface{}, version int) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writeValue(&buf, reflect.ValueOf(object),
|
||||
func(f reflect.StructField) (string, bool) {
|
||||
return filterField(f, version)
|
||||
})
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Evan Huus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
go-resiliency
|
||||
=============
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency?status.svg)](https://godoc.org/github.com/eapache/go-resiliency)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
Resiliency patterns for golang.
|
||||
Based in part on [Hystrix](https://github.com/Netflix/Hystrix),
|
||||
[Semian](https://github.com/Shopify/semian), and others.
|
||||
|
||||
Currently implemented patterns include:
|
||||
- circuit-breaker (in the `breaker` directory)
|
||||
- semaphore (in the `semaphore` directory)
|
||||
- deadline/timeout (in the `deadline` directory)
|
||||
- batching (in the `batcher` directory)
|
||||
- retriable (in the `retrier` directory)
|
||||
|
||||
Follows semantic versioning using https://gopkg.in/ - import from
|
||||
[`gopkg.in/eapache/go-resiliency.v1`](https://gopkg.in/eapache/go-resiliency.v1)
|
||||
for guaranteed API stability.
|
|
@ -0,0 +1,31 @@
|
|||
batcher
|
||||
=======
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/batcher?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/batcher)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
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)
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
circuit-breaker
|
||||
===============
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/breaker?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/breaker)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
The circuit-breaker resiliency pattern for golang.
|
||||
|
||||
Creating a breaker takes three parameters:
|
||||
- error threshold (for opening the breaker)
|
||||
- success threshold (for closing the breaker)
|
||||
- timeout (how long to keep the breaker open)
|
||||
|
||||
```go
|
||||
b := breaker.New(3, 1, 5*time.Second)
|
||||
|
||||
for {
|
||||
result := b.Run(func() error {
|
||||
// communicate with some external service and
|
||||
// return an error if the communication failed
|
||||
return nil
|
||||
})
|
||||
|
||||
switch result {
|
||||
case nil:
|
||||
// success!
|
||||
case breaker.ErrBreakerOpen:
|
||||
// our function wasn't run because the breaker was open
|
||||
default:
|
||||
// some other error
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,161 @@
|
|||
// Package breaker implements the circuit-breaker resiliency pattern for Go.
|
||||
package breaker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrBreakerOpen is the error returned from Run() when the function is not executed
|
||||
// because the breaker is currently open.
|
||||
var ErrBreakerOpen = errors.New("circuit breaker is open")
|
||||
|
||||
const (
|
||||
closed uint32 = iota
|
||||
open
|
||||
halfOpen
|
||||
)
|
||||
|
||||
// Breaker implements the circuit-breaker resiliency pattern
|
||||
type Breaker struct {
|
||||
errorThreshold, successThreshold int
|
||||
timeout time.Duration
|
||||
|
||||
lock sync.Mutex
|
||||
state uint32
|
||||
errors, successes int
|
||||
lastError time.Time
|
||||
}
|
||||
|
||||
// New constructs a new circuit-breaker that starts closed.
|
||||
// From closed, the breaker opens if "errorThreshold" errors are seen
|
||||
// without an error-free period of at least "timeout". From open, the
|
||||
// breaker half-closes after "timeout". From half-open, the breaker closes
|
||||
// after "successThreshold" consecutive successes, or opens on a single error.
|
||||
func New(errorThreshold, successThreshold int, timeout time.Duration) *Breaker {
|
||||
return &Breaker{
|
||||
errorThreshold: errorThreshold,
|
||||
successThreshold: successThreshold,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Run will either return ErrBreakerOpen immediately if the circuit-breaker is
|
||||
// already open, or it will run the given function and pass along its return
|
||||
// value. It is safe to call Run concurrently on the same Breaker.
|
||||
func (b *Breaker) Run(work func() error) error {
|
||||
state := atomic.LoadUint32(&b.state)
|
||||
|
||||
if state == open {
|
||||
return ErrBreakerOpen
|
||||
}
|
||||
|
||||
return b.doWork(state, work)
|
||||
}
|
||||
|
||||
// Go will either return ErrBreakerOpen immediately if the circuit-breaker is
|
||||
// already open, or it will run the given function in a separate goroutine.
|
||||
// If the function is run, Go will return nil immediately, and will *not* return
|
||||
// the return value of the function. It is safe to call Go concurrently on the
|
||||
// same Breaker.
|
||||
func (b *Breaker) Go(work func() error) error {
|
||||
state := atomic.LoadUint32(&b.state)
|
||||
|
||||
if state == open {
|
||||
return ErrBreakerOpen
|
||||
}
|
||||
|
||||
// errcheck complains about ignoring the error return value, but
|
||||
// that's on purpose; if you want an error from a goroutine you have to
|
||||
// get it over a channel or something
|
||||
go b.doWork(state, work)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Breaker) doWork(state uint32, work func() error) error {
|
||||
var panicValue interface{}
|
||||
|
||||
result := func() error {
|
||||
defer func() {
|
||||
panicValue = recover()
|
||||
}()
|
||||
return work()
|
||||
}()
|
||||
|
||||
if result == nil && panicValue == nil && state == closed {
|
||||
// short-circuit the normal, success path without contending
|
||||
// on the lock
|
||||
return nil
|
||||
}
|
||||
|
||||
// oh well, I guess we have to contend on the lock
|
||||
b.processResult(result, panicValue)
|
||||
|
||||
if panicValue != nil {
|
||||
// as close as Go lets us come to a "rethrow" although unfortunately
|
||||
// we lose the original panicing location
|
||||
panic(panicValue)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (b *Breaker) processResult(result error, panicValue interface{}) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if result == nil && panicValue == nil {
|
||||
if b.state == halfOpen {
|
||||
b.successes++
|
||||
if b.successes == b.successThreshold {
|
||||
b.closeBreaker()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if b.errors > 0 {
|
||||
expiry := b.lastError.Add(b.timeout)
|
||||
if time.Now().After(expiry) {
|
||||
b.errors = 0
|
||||
}
|
||||
}
|
||||
|
||||
switch b.state {
|
||||
case closed:
|
||||
b.errors++
|
||||
if b.errors == b.errorThreshold {
|
||||
b.openBreaker()
|
||||
} else {
|
||||
b.lastError = time.Now()
|
||||
}
|
||||
case halfOpen:
|
||||
b.openBreaker()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Breaker) openBreaker() {
|
||||
b.changeState(open)
|
||||
go b.timer()
|
||||
}
|
||||
|
||||
func (b *Breaker) closeBreaker() {
|
||||
b.changeState(closed)
|
||||
}
|
||||
|
||||
func (b *Breaker) timer() {
|
||||
time.Sleep(b.timeout)
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.changeState(halfOpen)
|
||||
}
|
||||
|
||||
func (b *Breaker) changeState(newState uint32) {
|
||||
b.errors = 0
|
||||
b.successes = 0
|
||||
atomic.StoreUint32(&b.state, newState)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
deadline
|
||||
========
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/deadline?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/deadline)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
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 potentially slow
|
||||
// give up when the `stopper` channel is closed (indicating a time-out)
|
||||
return nil
|
||||
})
|
||||
|
||||
switch err {
|
||||
case deadline.ErrTimedOut:
|
||||
// execution took too long, oops
|
||||
default:
|
||||
// some other error
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
retrier
|
||||
=======
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/retrier?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/retrier)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
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
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
semaphore
|
||||
=========
|
||||
|
||||
[![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency)
|
||||
[![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/semaphore?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/semaphore)
|
||||
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html)
|
||||
|
||||
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()
|
||||
```
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue