-
Notifications
You must be signed in to change notification settings - Fork 0
/
breaker.go
206 lines (175 loc) · 4.77 KB
/
breaker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
Package breaker provides a simple implementation of the circuite breaker
pattern. A typical use would be to protect a call to an external system.
By protecting calls to external systems with a circuit breaker, you are
able to respond quickly in the event of system failure, avoiding the
need to wait for a timeout.
cb := NewBreaker().TripAfter(5).ResetAfter(500)
Further information on the circuit breaker pattern can be found in the
Microsoft Azure Architecture Patterns documentation.
https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
*/
package breaker
import (
"errors"
"time"
)
// Breaker represents a circuit breaker. In normal use, an instance of
// the circuit breaker should be used to protect a single external
// system. Protecting multiple systems with a single instance of a
// circuit breaker is not recommended.
type Breaker struct {
failCount int
successCount int
lastFail time.Time
state State
shouldTrip stateFunc
shouldReset stateFunc
subscribers []chan State
}
// A StateFunc defines a function that can be used to determine a state
// change.
type stateFunc func() bool
// State represents the state of the circuit breaker
type State int
// Circuit breaker states
const (
StateOpen State = iota
StateClosed
StatePartial
)
func (s State) String() string {
switch s {
case StateClosed:
return "closed"
case StateOpen:
return "open"
case StatePartial:
return "partial"
default:
return "unknown"
}
}
// NewBreaker returns an instance of a circuit breaker using the default
// configuration.
//
// By default the circuit breaker will trip after 5 failed transactions,
// enter the partially open state after 50ms. Once in the partially open
// state it will reset if the next call is successful or trip if it fails.
func NewBreaker() *Breaker {
b := Breaker{}
b.state = StateClosed
b.TripAfter(5)
b.ResetAfter(50 * time.Millisecond)
return &b
}
// FailCount returns the current count of failed transactions.
func (b *Breaker) FailCount() int {
return b.failCount
}
// SuccessCount returns the current count of successful transactions.
func (b *Breaker) SuccessCount() int {
return b.successCount
}
// CurrentState returns the current state of the circuit breaker.
func (b *Breaker) CurrentState() State {
return b.state
}
// fail increments the failCount
func (b *Breaker) fail() {
b.failCount++
b.lastFail = time.Now()
}
// success increments the successCount
func (b *Breaker) success() {
b.successCount++
}
// Reset returns the fail and success counters to zero
func (b *Breaker) Reset() {
b.state = StateClosed
b.failCount = 0
b.successCount = 0
b.notify(StateClosed)
}
// partial returns the fail and success counters to zero
func (b *Breaker) partial() {
b.state = StatePartial
b.failCount = 0
b.successCount = 0
b.notify(StatePartial)
}
// trip opens the breaker
func (b *Breaker) trip() {
b.state = StateOpen
b.notify(StateOpen)
}
// Protect wraps a function that returns an error with the circuit
// breaker. If an error is returned, the breaker increments the
// failure counter. If a success is returned, the breaker increments
// the success counter.
//
// If the breaker is open, an error is returned indicating the current
// state of the breaker.
func (b *Breaker) Protect(f func() error) error {
// if the breaker is open and we are ready to reset then enter the
// partially open state
if b.CurrentState() == StateOpen {
if b.shouldReset() == false {
return errors.New("breaker open")
}
b.partial()
}
// pass through the next request and handle the response based on
// the current state of the breaker
err := f()
if err != nil {
b.fail()
if b.CurrentState() == StatePartial {
b.trip()
}
if b.shouldTrip() == true {
b.trip()
}
return err
}
// if we are in the partial state then reset the breaker
if b.CurrentState() == StatePartial {
b.Reset()
}
b.success()
return nil
}
// TripAfter configures the breaker to trip after n failed transactions.
// Note that these failed transactions do not need to occur consecutively.
func (b *Breaker) TripAfter(n int) *Breaker {
b.shouldTrip = func() bool {
return b.FailCount() >= n
}
return b
}
// ResetAfter configures the breaker to reset after a period of time since
// the last failure.
func (b *Breaker) ResetAfter(t time.Duration) *Breaker {
b.shouldReset = func() bool {
resetTime := b.lastFail.Add(t)
if time.Now().After(resetTime) {
return true
}
return false
}
return b
}
// Subscribe returns a channel on which consumers can receive notifications
// on state change.
func (b *Breaker) Subscribe() chan State {
c := make(chan State, 1)
b.subscribers = append(b.subscribers, c)
return c
}
func (b *Breaker) notify(state State) {
for _, s := range b.subscribers {
if len(s) < cap(s) {
s <- state
}
}
}