├── LICENSE ├── example_test.go ├── README.md ├── circuitbreaker_test.go ├── client.go └── circuitbreaker.go /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Scott Barron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package circuitbreaker 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func ExampleThresholdBreaker() { 11 | // This example sets up a ThresholdBreaker that will trip if remoteCall returns 12 | // an error 10 times in a row. The error returned by Call() will be the error 13 | // returned by remoteCall, unless the breaker has been tripped, in which case 14 | // it will return ErrBreakerOpen. 15 | breaker := NewThresholdBreaker(10) 16 | err := breaker.Call(remoteCall) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | } 21 | 22 | func ExampleThresholdBreaker_manual() { 23 | // This example demonstrates the manual use of a ThresholdBreaker. The breaker 24 | // will trip when Fail is called 10 times in a row. 25 | breaker := NewThresholdBreaker(10) 26 | if breaker.Ready() { 27 | err := remoteCall 28 | if err != nil { 29 | breaker.Fail() 30 | log.Fatal(err) 31 | } else { 32 | breaker.Reset() 33 | } 34 | } 35 | } 36 | 37 | func ExampleTimeoutBreaker() { 38 | // This example sets up a TimeoutBreaker that will trip if remoteCall returns 39 | // an error OR takes longer than one second 10 times in a row. The error returned 40 | // by Call() will be the error returned by remoteCall with two exceptions: if 41 | // remoteCall takes longer than one second the return value will be ErrBreakerTimeout, 42 | // if the breaker has been tripped the return value will be ErrBreakerOpen. 43 | breaker := NewTimeoutBreaker(time.Second, 10) 44 | err := breaker.Call(remoteCall) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | 50 | func ExampleHTTPClient() { 51 | // This example sets up an HTTP client wrapped in a TimeoutBreaker. The breaker 52 | // will trip with the same behavior as TimeoutBreaker. 53 | client := NewHTTPClient(time.Second*5, 10, nil) 54 | 55 | resp, err := client.Get("http://example.com/resource.json") 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | resource, err := ioutil.ReadAll(resp.Body) 60 | resp.Body.Close() 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | fmt.Printf("%s", resource) 65 | } 66 | 67 | func ExampleCircuitBreaker_callbacks() { 68 | // This example demonstrates the BreakerTripped and BreakerReset callbacks. These are 69 | // available on all breaker types. 70 | breaker := NewThresholdBreaker(1) 71 | breaker.BreakerTripped = func() { 72 | log.Println("breaker tripped") 73 | } 74 | breaker.BreakerReset = func() { 75 | log.Println("breaker reset") 76 | } 77 | 78 | breaker.Fail() 79 | breaker.Reset() 80 | } 81 | 82 | func remoteCall() error { 83 | // Expensive remote call 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # circuitbreaker 2 | 3 | Circuitbreaker provides an easy way to use the Circuit Breaker pattern in a 4 | Go program. 5 | 6 | Circuit breakers are typically used when your program makes remote calls. 7 | Remote calls can often hang for a while before they time out. If your 8 | application makes a lot of these requests, many resources can be tied 9 | up waiting for these time outs to occur. A circuit breaker wraps these 10 | remote calls and will trip after a defined amount of failures or time outs 11 | occur. When a circuit breaker is tripped any future calls will avoid making 12 | the remote call and return an error to the client. In the meantime, the 13 | circuit breaker will periodically allow some calls to be tried again and 14 | will close the circuit if those are successful. 15 | 16 | You can read more about this pattern and how it's used at: 17 | - [Martin Fowler's bliki](http://martinfowler.com/bliki/CircuitBreaker.html) 18 | - [The Netflix Tech Blog](http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html) 19 | - [Release It!](http://pragprog.com/book/mnee/release-it) 20 | 21 | [![GoDoc](https://godoc.org/github.com/rubyist/circuitbreaker?status.svg)](https://godoc.org/github.com/rubyist/circuitbreaker) 22 | 23 | ## Installation 24 | 25 | ``` 26 | go get github.com/rubyist/circuitbreaker 27 | ``` 28 | 29 | ## Examples 30 | 31 | Here is a quick example of what circuitbreaker provides 32 | 33 | ```go 34 | // Creates a circuit breaker that will trip if the function fails 10 times 35 | cb := NewThresholdBreaker(10) 36 | 37 | cb.BreakerTripped = func() { 38 | // This function will be called every time the circuit breaker moves 39 | // from reset to tripped. 40 | } 41 | 42 | cb.BreakerReset = func() { 43 | // This function will be called every time the circuit breaker moves 44 | // from tripped to reset. 45 | } 46 | 47 | cb.Call(func() error { 48 | // This is where you'll do some remote call 49 | // If it fails, return an error 50 | }) 51 | ``` 52 | 53 | Circuitbreaker can also wrap a time out around the remote call. 54 | 55 | ```go 56 | // Creates a circuit breaker that will trip after 10 failures or time outs 57 | // using a time out of 5 seconds 58 | cb := NewTimeoutBreaker(Time.Second * 5, 10) 59 | 60 | // Proceed as above 61 | 62 | ``` 63 | 64 | Circuitbreaker also provides a wrapper around `http.Client` that will wrap a 65 | time out around any request. 66 | 67 | ```go 68 | // Passing in nil will create a regular http.Client. 69 | // You can also build your own http.Client and pass it in 70 | client := NewHTTPClient(time.Second * 5, 10, nil) 71 | client.BreakerTripped = func() { 72 | // Perhaps notify your monitoring system 73 | } 74 | client.BreakerReset = func() { 75 | // Perhaps notify your monitoring system 76 | } 77 | 78 | resp, err := client.Get("http://example.com/resource.json") 79 | ``` 80 | 81 | See the godoc for more examples. 82 | 83 | ## Bugs, Issues, Feedback 84 | 85 | Right here on GitHub: [https://github.com/rubyist/circuitbreaker](https://github.com/rubyist/circuitbreaker) 86 | -------------------------------------------------------------------------------- /circuitbreaker_test.go: -------------------------------------------------------------------------------- 1 | package circuitbreaker 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestCircuitBreakerTripping(t *testing.T) { 11 | cb := &CircuitBreaker{} 12 | 13 | if cb.Tripped() { 14 | t.Fatal("expected breaker to not be tripped") 15 | } 16 | 17 | cb.Trip() 18 | if !cb.Tripped() { 19 | t.Fatal("expected breaker to be tripped") 20 | } 21 | 22 | cb.Reset() 23 | if cb.Tripped() { 24 | t.Fatal("expected breaker to have been reset") 25 | } 26 | } 27 | 28 | func TestCircuitBreakerCallbacks(t *testing.T) { 29 | trippedCalled := false 30 | resetCalled := false 31 | 32 | var wg sync.WaitGroup 33 | wg.Add(2) 34 | 35 | cb := &CircuitBreaker{} 36 | cb.BreakerTripped = func() { 37 | trippedCalled = true 38 | wg.Done() 39 | } 40 | cb.BreakerReset = func() { 41 | resetCalled = true 42 | wg.Done() 43 | } 44 | 45 | cb.Trip() 46 | cb.Reset() 47 | 48 | wg.Wait() 49 | 50 | if !trippedCalled { 51 | t.Fatal("expected BreakerOpen to have been called") 52 | } 53 | 54 | if !resetCalled { 55 | t.Fatal("expected BreakerClosed to have been called") 56 | } 57 | } 58 | 59 | func TestResettingBreakerState(t *testing.T) { 60 | cb := NewResettingBreaker(time.Millisecond * 100) 61 | 62 | if cb.state() != closed { 63 | t.Fatal("expected resetting breaker to start closed") 64 | } 65 | 66 | cb.Fail() 67 | cb.Trip() 68 | if cb.state() != open { 69 | t.Fatal("expected resetting breaker to be open") 70 | } 71 | 72 | time.Sleep(cb.ResetTimeout) 73 | if cb.state() != halfopen { 74 | t.Fatal("expected resetting breaker to indicate a reattempt") 75 | } 76 | } 77 | 78 | func TestThresholdBreaker(t *testing.T) { 79 | cb := NewThresholdBreaker(2) 80 | 81 | if cb.Tripped() { 82 | t.Fatal("expected threshold breaker to be open") 83 | } 84 | 85 | cb.Fail() 86 | if cb.Tripped() { 87 | t.Fatal("expected threshold breaker to still be open") 88 | } 89 | 90 | cb.Fail() 91 | if !cb.Tripped() { 92 | t.Fatal("expected threshold breaker to be tripped") 93 | } 94 | 95 | cb.Reset() 96 | if cb.failures != 0 { 97 | t.Fatalf("expected reset to set failures to 0, got %d", cb.failures) 98 | } 99 | if cb.Tripped() { 100 | t.Fatal("expected threshold breaker to be open") 101 | } 102 | } 103 | 104 | func TestThresholdBreakerCalling(t *testing.T) { 105 | circuit := func() error { 106 | return fmt.Errorf("error") 107 | } 108 | 109 | cb := NewThresholdBreaker(2) 110 | cb.ResetTimeout = time.Second 111 | 112 | err := cb.Call(circuit) // First failure 113 | if err == nil { 114 | t.Fatal("expected threshold breaker to error") 115 | } 116 | if cb.Tripped() { 117 | t.Fatal("expected threshold breaker to be open") 118 | } 119 | 120 | err = cb.Call(circuit) // Second failure trips 121 | if err == nil { 122 | t.Fatal("expected threshold breaker to error") 123 | } 124 | if !cb.Tripped() { 125 | t.Fatal("expected threshold breaker to be tripped") 126 | } 127 | } 128 | 129 | func TestThresholdBreakerResets(t *testing.T) { 130 | called := 0 131 | success := false 132 | circuit := func() error { 133 | if called == 0 { 134 | called++ 135 | return fmt.Errorf("error") 136 | } 137 | success = true 138 | return nil 139 | } 140 | 141 | cb := NewThresholdBreaker(1) 142 | err := cb.Call(circuit) 143 | if err == nil { 144 | t.Fatal("Expected cb to return an error") 145 | } 146 | 147 | time.Sleep(time.Millisecond * 500) 148 | err = cb.Call(circuit) 149 | if err != nil { 150 | t.Fatal("Expected cb to be successful") 151 | } 152 | 153 | if !success { 154 | t.Fatal("Expected cb to have been reset") 155 | } 156 | } 157 | 158 | func TestTimeoutBreaker(t *testing.T) { 159 | called := 0 160 | circuit := func() error { 161 | called++ 162 | time.Sleep(time.Millisecond * 150) 163 | return nil 164 | } 165 | 166 | cb := NewTimeoutBreaker(time.Millisecond*100, 1) 167 | err := cb.Call(circuit) 168 | if err == nil { 169 | t.Fatal("expected timeout breaker to return an error") 170 | } 171 | cb.Call(circuit) 172 | 173 | if !cb.Tripped() { 174 | t.Fatal("expected timeout breaker to be open") 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package circuitbreaker 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/url" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities. 12 | // 13 | // By default, the client will use its defaultBreaker. A BreakerLookup function may be 14 | // provided to allow different breakers to be used based on the circumstance. See the 15 | // implementation of NewHostBasedHTTPClient for an example of this. 16 | type HTTPClient struct { 17 | Client *http.Client 18 | BreakerTripped func() 19 | BreakerReset func() 20 | BreakerLookup func(*HTTPClient, interface{}) *TimeoutBreaker 21 | defaultBreaker *TimeoutBreaker 22 | breakers map[interface{}]*TimeoutBreaker 23 | breakerLock sync.Mutex 24 | } 25 | 26 | // NewCircuitBreakerClient provides a circuit breaker wrapper around http.Client. 27 | // It wraps all of the regular http.Client functions. Specifying 0 for timeout will 28 | // give a breaker that does not check for time outs. 29 | func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient { 30 | if client == nil { 31 | client = &http.Client{} 32 | } 33 | 34 | breaker := NewTimeoutBreaker(timeout, threshold) 35 | breakers := make(map[interface{}]*TimeoutBreaker, 0) 36 | brclient := &HTTPClient{Client: client, defaultBreaker: breaker, breakers: breakers} 37 | breaker.BreakerTripped = brclient.runBreakerTripped 38 | breaker.BreakerReset = brclient.runBreakerReset 39 | return brclient 40 | } 41 | 42 | // NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This 43 | // client will use one circuit breaker per host parsed from the request URL. This allows 44 | // you to use a single HTTPClient for multiple hosts with one host's breaker not affecting 45 | // the other hosts. 46 | func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient { 47 | brclient := NewHTTPClient(timeout, threshold, client) 48 | 49 | brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *TimeoutBreaker { 50 | rawURL := val.(string) 51 | parsedURL, err := url.Parse(rawURL) 52 | if err != nil { 53 | return c.defaultBreaker 54 | } 55 | host := parsedURL.Host 56 | 57 | c.breakerLock.Lock() 58 | defer c.breakerLock.Unlock() 59 | cb, ok := c.breakers[host] 60 | if !ok { 61 | cb = NewTimeoutBreaker(timeout, threshold) 62 | cb.BreakerTripped = brclient.runBreakerTripped 63 | cb.BreakerReset = brclient.runBreakerReset 64 | c.breakers[host] = cb 65 | } 66 | return cb 67 | } 68 | 69 | return brclient 70 | } 71 | 72 | // Do wraps http.Client Do() 73 | func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) { 74 | var resp *http.Response 75 | var err error 76 | breaker := c.breakerLookup(req.URL.String()) 77 | breaker.Call(func() error { 78 | resp, err = c.Client.Do(req) 79 | return err 80 | }) 81 | return resp, err 82 | } 83 | 84 | // Get wraps http.Client Get() 85 | func (c *HTTPClient) Get(url string) (*http.Response, error) { 86 | var resp *http.Response 87 | breaker := c.breakerLookup(url) 88 | err := breaker.Call(func() error { 89 | aresp, err := c.Client.Get(url) 90 | resp = aresp 91 | return err 92 | }) 93 | return resp, err 94 | } 95 | 96 | // Head wraps http.Client Head() 97 | func (c *HTTPClient) Head(url string) (*http.Response, error) { 98 | var resp *http.Response 99 | breaker := c.breakerLookup(url) 100 | err := breaker.Call(func() error { 101 | aresp, err := c.Client.Head(url) 102 | resp = aresp 103 | return err 104 | }) 105 | return resp, err 106 | } 107 | 108 | // Post wraps http.Client Post() 109 | func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) { 110 | var resp *http.Response 111 | breaker := c.breakerLookup(url) 112 | err := breaker.Call(func() error { 113 | aresp, err := c.Client.Post(url, bodyType, body) 114 | resp = aresp 115 | return err 116 | }) 117 | return resp, err 118 | } 119 | 120 | // PostForm wraps http.Client PostForm() 121 | func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) { 122 | var resp *http.Response 123 | breaker := c.breakerLookup(url) 124 | err := breaker.Call(func() error { 125 | aresp, err := c.Client.PostForm(url, data) 126 | resp = aresp 127 | return err 128 | }) 129 | return resp, err 130 | } 131 | 132 | func (c *HTTPClient) breakerLookup(val interface{}) *TimeoutBreaker { 133 | if c.BreakerLookup != nil { 134 | return c.BreakerLookup(c, val) 135 | } 136 | return c.defaultBreaker 137 | } 138 | 139 | func (c *HTTPClient) runBreakerTripped() { 140 | if c.BreakerTripped != nil { 141 | c.BreakerTripped() 142 | } 143 | } 144 | 145 | func (c *HTTPClient) runBreakerReset() { 146 | if c.BreakerReset != nil { 147 | c.BreakerReset() 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /circuitbreaker.go: -------------------------------------------------------------------------------- 1 | // Package circuitbreaker implements the Circuit Breaker pattern. It will wrap 2 | // a function call (typically one which uses remote services) and monitors for 3 | // failures and/or time outs. When a threshold of failures or time outs has been 4 | // reached, future calls to the function will not run. During this state, the 5 | // breaker will periodically allow the function to run and, if it is successful, 6 | // will start running the function again. 7 | // 8 | // The package also provides a wrapper around an http.Client that wraps all of 9 | // the http.Client functions with a CircuitBreaker. 10 | // 11 | package circuitbreaker 12 | 13 | import ( 14 | "errors" 15 | "sync/atomic" 16 | "time" 17 | "unsafe" 18 | ) 19 | 20 | type state int 21 | 22 | const ( 23 | open state = iota 24 | halfopen state = iota 25 | closed state = iota 26 | ) 27 | 28 | // Error codes returned by Call 29 | var ( 30 | ErrBreakerOpen = errors.New("breaker open") 31 | ErrBreakerTimeout = errors.New("breaker time out") 32 | ) 33 | 34 | // CircuitBreaker is a base for building trippable circuit breakers. It provides 35 | // two fields for functions, BreakerTripped and BreakerReset that will run 36 | // when the circuit breaker is tripped and reset, respectively. 37 | type CircuitBreaker struct { 38 | // BreakerTripped, if set, will be called whenever the CircuitBreaker 39 | // moves from the reset state to the tripped state. 40 | BreakerTripped func() 41 | 42 | // BreakerReset, if set, will be called whenever the CircuitBreaker 43 | // moves from the tripped state to the reset state. 44 | BreakerReset func() 45 | 46 | tripped int32 47 | } 48 | 49 | // Trip will trip the circuit breaker. After Trip() is called, Tripped() will 50 | // return true. If a BreakerTripped callback is available it will be run. 51 | func (cb *CircuitBreaker) Trip() { 52 | atomic.StoreInt32(&cb.tripped, 1) 53 | if cb.BreakerTripped != nil { 54 | go cb.BreakerTripped() 55 | } 56 | } 57 | 58 | // Reset will reset the circuit breaker. After Reset() is called, Tripped() will 59 | // return false. If a BreakerReset callback is available it will be run. 60 | func (cb *CircuitBreaker) Reset() { 61 | atomic.StoreInt32(&cb.tripped, 0) 62 | if cb.BreakerReset != nil { 63 | go cb.BreakerReset() 64 | } 65 | } 66 | 67 | // Tripped returns true if the circuit breaker is tripped, false if it is reset. 68 | func (cb *CircuitBreaker) Tripped() bool { 69 | return cb.tripped == 1 70 | } 71 | 72 | // ResettingBreaker is used to build circuit breakers that will attempt to 73 | // automatically reset themselves after a certain period of time since the 74 | // last failure. 75 | type ResettingBreaker struct { 76 | // ResetTimeout is the minimum amount of time the CircuitBreaker will wait 77 | // before allowing the function to be called again 78 | ResetTimeout time.Duration 79 | 80 | _lastFailure unsafe.Pointer 81 | halfOpens int64 82 | *CircuitBreaker 83 | } 84 | 85 | // NewResettingBreaker returns a new ResettingBreaker with the given reset timeout 86 | func NewResettingBreaker(resetTimeout time.Duration) *ResettingBreaker { 87 | return &ResettingBreaker{resetTimeout, nil, 0, &CircuitBreaker{}} 88 | } 89 | 90 | // Trip will trip the circuit breaker. After Trip() is called, Tripped() will 91 | // return true. If a BreakerTripped callback is available it will be run. 92 | func (cb *ResettingBreaker) Trip() { 93 | cb.Fail() 94 | cb.CircuitBreaker.Trip() 95 | } 96 | 97 | // Fail records the time of a failure 98 | func (cb *ResettingBreaker) Fail() { 99 | now := time.Now() 100 | atomic.StorePointer(&cb._lastFailure, unsafe.Pointer(&now)) 101 | } 102 | 103 | // Ready will return true if the circuit breaker is ready to call the function. 104 | // It will be ready if the breaker is in a reset state, or if it is time to retry 105 | // the call for auto resetting. 106 | func (cb *ResettingBreaker) Ready() bool { 107 | state := cb.state() 108 | return state == closed || state == halfopen 109 | } 110 | 111 | // State returns the state of the ResettingBreaker. The states available are: 112 | // closed - the circuit is in a reset state and is operational 113 | // open - the circuit is in a tripped state 114 | // halfopen - the circuit is in a tripped state but the reset timeout has passed 115 | func (cb *ResettingBreaker) state() state { 116 | tripped := cb.Tripped() 117 | if tripped { 118 | since := time.Since(cb.lastFailure()) 119 | if since > cb.ResetTimeout { 120 | if atomic.CompareAndSwapInt64(&cb.halfOpens, 0, 1) { 121 | return halfopen 122 | } 123 | return open 124 | } 125 | return open 126 | } 127 | return closed 128 | } 129 | 130 | func (cb *ResettingBreaker) lastFailure() time.Time { 131 | ptr := atomic.LoadPointer(&cb._lastFailure) 132 | return *(*time.Time)(ptr) 133 | } 134 | 135 | // ThresholdBreaker is a ResettingCircuitBreaker that will trip when its failure count 136 | // passes a given threshold. Clients of ThresholdBreaker can either manually call the 137 | // Fail function to record a failure, checking the tripped state themselves, or they 138 | // can use the Call function to wrap the ThresholdBreaker around a function call. 139 | type ThresholdBreaker struct { 140 | // Threshold is the number of failures CircuitBreaker will allow before tripping 141 | Threshold int64 142 | 143 | failures int64 144 | 145 | *ResettingBreaker 146 | } 147 | 148 | // NewThresholdBreaker creates a new ThresholdBreaker with the given failure threshold. 149 | func NewThresholdBreaker(threshold int64) *ThresholdBreaker { 150 | return &ThresholdBreaker{threshold, 0, NewResettingBreaker(time.Millisecond * 500)} 151 | } 152 | 153 | // Fail records a failure. If the failure count meets the threshold, the circuit breaker 154 | // will trip. If a BreakerTripped callback is available it will be run. 155 | func (cb *ThresholdBreaker) Fail() { 156 | if cb.Tripped() { 157 | return 158 | } 159 | 160 | cb.ResettingBreaker.Fail() 161 | failures := atomic.AddInt64(&cb.failures, 1) 162 | if failures == cb.Threshold { 163 | cb.Trip() 164 | if cb.BreakerTripped != nil { 165 | cb.BreakerTripped() 166 | } 167 | } 168 | } 169 | 170 | // Reset will reset the circuit breaker. After Reset() is called, Tripped() will 171 | // return false. If a BreakerReset callback is available it will be run. 172 | func (cb *ThresholdBreaker) Reset() int64 { 173 | cb.ResettingBreaker.Reset() 174 | return atomic.SwapInt64(&cb.failures, 0) 175 | } 176 | 177 | // Call wraps the function the ThresholdBreaker will protect. A failure is recorded 178 | // whenever the function returns an error. If the threshold is met, the ThresholdBreaker 179 | // will trip. 180 | func (cb *ThresholdBreaker) Call(circuit func() error) error { 181 | state := cb.state() 182 | 183 | if state == open { 184 | return ErrBreakerOpen 185 | } 186 | 187 | err := circuit() 188 | 189 | if err != nil { 190 | if state == halfopen { 191 | atomic.StoreInt64(&cb.halfOpens, 0) 192 | } 193 | 194 | cb.Fail() 195 | 196 | return err 197 | } 198 | 199 | cb.Reset() 200 | 201 | return nil 202 | } 203 | 204 | // ThresholdBreaker is a ThresholdBreaker that will record a failure if the function 205 | // it is protecting takes too long to run. Clients of Timeout must use the Call function. 206 | // The Fail function is a noop. 207 | type TimeoutBreaker struct { 208 | // Timeout is the length of time the CircuitBreaker will wait for Call() to finish 209 | Timeout time.Duration 210 | *ThresholdBreaker 211 | } 212 | 213 | // NewTimeoutBreaker returns a new TimeoutBreaker with the given call timeout and failure threshold. 214 | // If timeout is specified as 0 then no timeout will be used and the behavior will be the 215 | // same as a ThresholdBreaker 216 | func NewTimeoutBreaker(timeout time.Duration, threshold int64) *TimeoutBreaker { 217 | return &TimeoutBreaker{timeout, NewThresholdBreaker(threshold)} 218 | } 219 | 220 | // Fail is a noop for a TimeoutBreaker. Clients must use Call() 221 | func (cb *TimeoutBreaker) Fail() { 222 | } 223 | 224 | // Call wraps the function the TimeoutBreaker will protect. A failure is recorded 225 | // whenever the function returns an error. If the threshold is met, the TimeoutBreaker 226 | // will trip. 227 | func (cb *TimeoutBreaker) Call(circuit func() error) error { 228 | c := make(chan int, 1) 229 | var err error 230 | 231 | if cb.Timeout == 0 { 232 | return cb.ThresholdBreaker.Call(circuit) 233 | } 234 | 235 | go func() { 236 | err = cb.ThresholdBreaker.Call(circuit) 237 | close(c) 238 | }() 239 | select { 240 | case <-c: 241 | if err != nil && err != ErrBreakerOpen { 242 | cb.ThresholdBreaker.Fail() 243 | } 244 | return err 245 | case <-time.After(cb.Timeout): 246 | cb.ThresholdBreaker.Fail() 247 | return ErrBreakerTimeout 248 | } 249 | } 250 | --------------------------------------------------------------------------------