Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix go mod issue based on Custom checker for half-open state like ReadyToTrip branch #38

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ gobreaker
=========

[![GoDoc](https://godoc.org/github.com/sony/gobreaker?status.svg)](http://godoc.org/github.com/sony/gobreaker)
[![Build Status](https://travis-ci.org/sony/gobreaker.svg?branch=master)](https://travis-ci.org/sony/gobreaker)
[![Coverage Status](https://coveralls.io/repos/sony/gobreaker/badge.svg?branch=master&service=github)](https://coveralls.io/github/sony/gobreaker?branch=master)

[gobreaker][repo-url] implements the [Circuit Breaker pattern](https://msdn.microsoft.com/en-us/library/dn589784.aspx) in Go.

Installation
------------

```
go get github.com/sony/gobreaker
go get github.com/rocyou/gobreaker
```

Usage
Expand All @@ -27,20 +29,21 @@ You can configure `CircuitBreaker` by the struct `Settings`:
```go
type Settings struct {
Name string
MaxRequests uint32
ReadyToClose func(counts Counts) (bool, bool)
Interval time.Duration
Timeout time.Duration
ReadyToTrip func(counts Counts) bool
OnStateChange func(name string, from State, to State)
IsSuccessful func(err error) bool
}
```

- `Name` is the name of the `CircuitBreaker`.

- `MaxRequests` is the maximum number of requests allowed to pass through
when the `CircuitBreaker` is half-open.
If `MaxRequests` is 0, `CircuitBreaker` allows only 1 request.
- `ReadyToClose` is called with a copy of `Counts` for each request in the half-open state.
If `ReadyToClose` returns true, the `CircuitBreaker` will be placed into the close state.
If `ReadyToClose` returns false, the `CircuitBreaker` will be placed into the open state if second returned value is true.
If `ReadyToClose` is nil, default `ReadyToClose` is used.
Default `ReadyToClose` returns true when the number of consecutive successes is more than 1.

- `Interval` is the cyclic period of the closed state
for `CircuitBreaker` to clear the internal `Counts`, described later in this section.
Expand All @@ -57,11 +60,6 @@ type Settings struct {

- `OnStateChange` is called whenever the state of `CircuitBreaker` changes.

- `IsSuccessful` is called with the error returned from a request.
If `IsSuccessful` returns true, the error is counted as a success.
Otherwise the error is counted as a failure.
If `IsSuccessful` is nil, default `IsSuccessful` is used, which returns false for all non-nil errors.

The struct `Counts` holds the numbers of requests and their successes/failures:

```go
Expand Down
26 changes: 19 additions & 7 deletions example/http_breaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"io/ioutil"
"log"
"net/http"
"time"

"github.com/sony/gobreaker"
"github.com/rocyou/gobreaker"
)

var cb *gobreaker.CircuitBreaker
Expand All @@ -15,10 +16,24 @@ func init() {
var st gobreaker.Settings
st.Name = "HTTP GET"
st.ReadyToTrip = func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 3 && failureRatio >= 0.6
return counts.ConsecutiveFailures >= 3
}
st.Interval = time.Duration(0) * time.Second
st.Timeout = time.Duration(3) * time.Second
st.ReadyToClose = func(counts gobreaker.Counts) (bool, bool) {
if counts.TotalSuccesses >= 2 {
return true, true
}
var nowOpen bool
if counts.ConsecutiveFailures >= 3 {
nowOpen = true
}
return false, nowOpen
}
st.OnStateChange = func(name string, from gobreaker.State, to gobreaker.State) {
fmt.Printf("change state from:%+v to:%+v\n", from, to)
//implement your action here
}

cb = gobreaker.NewCircuitBreaker(st)
}

Expand All @@ -29,19 +44,16 @@ func Get(url string) ([]byte, error) {
if err != nil {
return nil, err
}

defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return body, nil
})
if err != nil {
return nil, err
}

return body.([]byte), nil
}

Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/sony/gobreaker
module github.com/rocyou/gobreaker

go 1.12
go 1.16

require github.com/stretchr/testify v1.3.0
require github.com/stretchr/testify v1.7.0
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
67 changes: 24 additions & 43 deletions gobreaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ const (
)

var (
// ErrTooManyRequests is returned when the CB state is half open and the requests count is over the cb maxRequests
ErrTooManyRequests = errors.New("too many requests")
// ErrOpenState is returned when the CB state is open
ErrOpenState = errors.New("circuit breaker is open")
)
Expand Down Expand Up @@ -80,9 +78,11 @@ func (c *Counts) clear() {
//
// Name is the name of the CircuitBreaker.
//
// MaxRequests is the maximum number of requests allowed to pass through
// when the CircuitBreaker is half-open.
// If MaxRequests is 0, the CircuitBreaker allows only 1 request.
// ReadyToClose is called with a copy of Counts for each request in the half-open state.
// If ReadyToClose returns true, the CircuitBreaker will be placed into the close state.
// If ReadyToClose returns false, the CircuitBreaker will be placed into the open state if second returned value is true.
// If ReadyToClose is nil, default ReadyToClose is used.
// Default ReadyToClose returns true when the number of consecutive successes is more than 1.
//
// Interval is the cyclic period of the closed state
// for the CircuitBreaker to clear the internal Counts.
Expand All @@ -98,29 +98,22 @@ func (c *Counts) clear() {
// Default ReadyToTrip returns true when the number of consecutive failures is more than 5.
//
// OnStateChange is called whenever the state of the CircuitBreaker changes.
//
// IsSuccessful is called with the error returned from a request.
// If IsSuccessful returns true, the error is counted as a success.
// Otherwise the error is counted as a failure.
// If IsSuccessful is nil, default IsSuccessful is used, which returns false for all non-nil errors.
type Settings struct {
Name string
MaxRequests uint32
ReadyToClose func(counts Counts) (bool, bool)
Interval time.Duration
Timeout time.Duration
ReadyToTrip func(counts Counts) bool
OnStateChange func(name string, from State, to State)
IsSuccessful func(err error) bool
}

// CircuitBreaker is a state machine to prevent sending requests that are likely to fail.
type CircuitBreaker struct {
name string
maxRequests uint32
readyToClose func(counts Counts) (bool, bool)
interval time.Duration
timeout time.Duration
readyToTrip func(counts Counts) bool
isSuccessful func(err error) bool
onStateChange func(name string, from State, to State)

mutex sync.Mutex
Expand All @@ -144,10 +137,10 @@ func NewCircuitBreaker(st Settings) *CircuitBreaker {
cb.name = st.Name
cb.onStateChange = st.OnStateChange

if st.MaxRequests == 0 {
cb.maxRequests = 1
if st.ReadyToClose == nil {
cb.readyToClose = defaultReadyToClose
} else {
cb.maxRequests = st.MaxRequests
cb.readyToClose = st.ReadyToClose
}

if st.Interval <= 0 {
Expand All @@ -168,12 +161,6 @@ func NewCircuitBreaker(st Settings) *CircuitBreaker {
cb.readyToTrip = st.ReadyToTrip
}

if st.IsSuccessful == nil {
cb.isSuccessful = defaultIsSuccessful
} else {
cb.isSuccessful = st.IsSuccessful
}

cb.toNewGeneration(time.Now())

return cb
Expand All @@ -193,8 +180,8 @@ func defaultReadyToTrip(counts Counts) bool {
return counts.ConsecutiveFailures > 5
}

func defaultIsSuccessful(err error) bool {
return err == nil
func defaultReadyToClose(counts Counts) (bool, bool) {
return counts.ConsecutiveSuccesses >= 1, true
}

// Name returns the name of the CircuitBreaker.
Expand All @@ -212,14 +199,6 @@ func (cb *CircuitBreaker) State() State {
return state
}

// Counts returns internal counters
func (cb *CircuitBreaker) Counts() Counts {
cb.mutex.Lock()
defer cb.mutex.Unlock()

return cb.counts
}

// Execute runs the given request if the CircuitBreaker accepts it.
// Execute returns an error instantly if the CircuitBreaker rejects the request.
// Otherwise, Execute returns the result of the request.
Expand All @@ -240,7 +219,7 @@ func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{},
}()

result, err := req()
cb.afterRequest(generation, cb.isSuccessful(err))
cb.afterRequest(generation, err == nil)
return result, err
}

Expand All @@ -254,11 +233,6 @@ func (tscb *TwoStepCircuitBreaker) State() State {
return tscb.cb.State()
}

// Counts returns internal counters
func (tscb *TwoStepCircuitBreaker) Counts() Counts {
return tscb.cb.Counts()
}

// Allow checks if a new request can proceed. It returns a callback that should be used to
// register the success or failure in a separate step. If the circuit breaker doesn't allow
// requests, it returns an error.
Expand All @@ -282,8 +256,6 @@ func (cb *CircuitBreaker) beforeRequest() (uint64, error) {

if state == StateOpen {
return generation, ErrOpenState
} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
return generation, ErrTooManyRequests
}

cb.counts.onRequest()
Expand Down Expand Up @@ -313,7 +285,7 @@ func (cb *CircuitBreaker) onSuccess(state State, now time.Time) {
cb.counts.onSuccess()
case StateHalfOpen:
cb.counts.onSuccess()
if cb.counts.ConsecutiveSuccesses >= cb.maxRequests {
if ok, _ := cb.readyToClose(cb.counts); ok {
cb.setState(StateClosed, now)
}
}
Expand All @@ -326,8 +298,13 @@ func (cb *CircuitBreaker) onFailure(state State, now time.Time) {
if cb.readyToTrip(cb.counts) {
cb.setState(StateOpen, now)
}

case StateHalfOpen:
cb.setState(StateOpen, now)
cb.counts.onFailure()
ok, nowOpen := cb.readyToClose(cb.counts)
if !ok && nowOpen {
cb.setState(StateOpen, now)
}
}
}

Expand All @@ -337,11 +314,13 @@ func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
if !cb.expiry.IsZero() && cb.expiry.Before(now) {
cb.toNewGeneration(now)
}

case StateOpen:
if cb.expiry.Before(now) {
cb.setState(StateHalfOpen, now)
}
}

return cb.state, cb.generation
}

Expand Down Expand Up @@ -372,8 +351,10 @@ func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
} else {
cb.expiry = now.Add(cb.interval)
}

case StateOpen:
cb.expiry = now.Add(cb.timeout)

default: // StateHalfOpen
cb.expiry = zero
}
Expand Down
Loading