Skip to content

Commit 3ae3046

Browse files
committed
Add breaker.GetState() method to inspect state
This is useful in a few scenarios where the user might want to check the breaker state directly (perhaps for monitoring purposes), but also makes the tests much clearer because we can just assert on the expected breaker state directly.
1 parent 808c606 commit 3ae3046

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

breaker/breaker.go

+27-18
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import (
1212
// because the breaker is currently open.
1313
var ErrBreakerOpen = errors.New("circuit breaker is open")
1414

15+
// State is a type representing the possible states of a circuit breaker.
16+
type State uint32
17+
1518
const (
16-
closed uint32 = iota
17-
open
18-
halfOpen
19+
Closed State = iota
20+
Open
21+
HalfOpen
1922
)
2023

2124
// Breaker implements the circuit-breaker resiliency pattern
@@ -24,7 +27,7 @@ type Breaker struct {
2427
timeout time.Duration
2528

2629
lock sync.Mutex
27-
state uint32
30+
state State
2831
errors, successes int
2932
lastError time.Time
3033
}
@@ -46,9 +49,9 @@ func New(errorThreshold, successThreshold int, timeout time.Duration) *Breaker {
4649
// already open, or it will run the given function and pass along its return
4750
// value. It is safe to call Run concurrently on the same Breaker.
4851
func (b *Breaker) Run(work func() error) error {
49-
state := atomic.LoadUint32(&b.state)
52+
state := b.GetState()
5053

51-
if state == open {
54+
if state == Open {
5255
return ErrBreakerOpen
5356
}
5457

@@ -61,9 +64,9 @@ func (b *Breaker) Run(work func() error) error {
6164
// the return value of the function. It is safe to call Go concurrently on the
6265
// same Breaker.
6366
func (b *Breaker) Go(work func() error) error {
64-
state := atomic.LoadUint32(&b.state)
67+
state := b.GetState()
6568

66-
if state == open {
69+
if state == Open {
6770
return ErrBreakerOpen
6871
}
6972

@@ -75,7 +78,13 @@ func (b *Breaker) Go(work func() error) error {
7578
return nil
7679
}
7780

78-
func (b *Breaker) doWork(state uint32, work func() error) error {
81+
// GetState returns the current State of the circuit-breaker at the moment
82+
// that it is called.
83+
func (b *Breaker) GetState() State {
84+
return (State)(atomic.LoadUint32((*uint32)(&b.state)))
85+
}
86+
87+
func (b *Breaker) doWork(state State, work func() error) error {
7988
var panicValue interface{}
8089

8190
result := func() error {
@@ -85,7 +94,7 @@ func (b *Breaker) doWork(state uint32, work func() error) error {
8594
return work()
8695
}()
8796

88-
if result == nil && panicValue == nil && state == closed {
97+
if result == nil && panicValue == nil && state == Closed {
8998
// short-circuit the normal, success path without contending
9099
// on the lock
91100
return nil
@@ -108,7 +117,7 @@ func (b *Breaker) processResult(result error, panicValue interface{}) {
108117
defer b.lock.Unlock()
109118

110119
if result == nil && panicValue == nil {
111-
if b.state == halfOpen {
120+
if b.state == HalfOpen {
112121
b.successes++
113122
if b.successes == b.successThreshold {
114123
b.closeBreaker()
@@ -123,26 +132,26 @@ func (b *Breaker) processResult(result error, panicValue interface{}) {
123132
}
124133

125134
switch b.state {
126-
case closed:
135+
case Closed:
127136
b.errors++
128137
if b.errors == b.errorThreshold {
129138
b.openBreaker()
130139
} else {
131140
b.lastError = time.Now()
132141
}
133-
case halfOpen:
142+
case HalfOpen:
134143
b.openBreaker()
135144
}
136145
}
137146
}
138147

139148
func (b *Breaker) openBreaker() {
140-
b.changeState(open)
149+
b.changeState(Open)
141150
go b.timer()
142151
}
143152

144153
func (b *Breaker) closeBreaker() {
145-
b.changeState(closed)
154+
b.changeState(Closed)
146155
}
147156

148157
func (b *Breaker) timer() {
@@ -151,11 +160,11 @@ func (b *Breaker) timer() {
151160
b.lock.Lock()
152161
defer b.lock.Unlock()
153162

154-
b.changeState(halfOpen)
163+
b.changeState(HalfOpen)
155164
}
156165

157-
func (b *Breaker) changeState(newState uint32) {
166+
func (b *Breaker) changeState(newState State) {
158167
b.errors = 0
159168
b.successes = 0
160-
atomic.StoreUint32(&b.state, newState)
169+
atomic.StoreUint32((*uint32)(&b.state), (uint32)(newState))
161170
}

breaker/breaker_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,36 @@ func returnsSuccess() error {
2222

2323
func TestBreakerErrorExpiry(t *testing.T) {
2424
breaker := New(2, 1, 10*time.Millisecond)
25+
if breaker.GetState() != Closed {
26+
t.Error("incorrect state")
27+
}
2528

2629
for i := 0; i < 3; i++ {
2730
if err := breaker.Run(returnsError); err != errSomeError {
2831
t.Error(err)
2932
}
3033
time.Sleep(10 * time.Millisecond)
3134
}
35+
if breaker.GetState() != Closed {
36+
t.Error("incorrect state")
37+
}
3238

3339
for i := 0; i < 3; i++ {
3440
if err := breaker.Go(returnsError); err != nil {
3541
t.Error(err)
3642
}
3743
time.Sleep(10 * time.Millisecond)
3844
}
45+
if breaker.GetState() != Closed {
46+
t.Error("incorrect state")
47+
}
3948
}
4049

4150
func TestBreakerPanicsCountAsErrors(t *testing.T) {
4251
breaker := New(3, 2, 1*time.Second)
52+
if breaker.GetState() != Closed {
53+
t.Error("incorrect state")
54+
}
4355

4456
// three errors opens the breaker
4557
for i := 0; i < 3; i++ {
@@ -58,6 +70,9 @@ func TestBreakerPanicsCountAsErrors(t *testing.T) {
5870
}
5971

6072
// breaker is open
73+
if breaker.GetState() != Open {
74+
t.Error("incorrect state")
75+
}
6176
for i := 0; i < 5; i++ {
6277
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
6378
t.Error(err)
@@ -67,6 +82,9 @@ func TestBreakerPanicsCountAsErrors(t *testing.T) {
6782

6883
func TestBreakerStateTransitions(t *testing.T) {
6984
breaker := New(3, 2, 10*time.Millisecond)
85+
if breaker.GetState() != Closed {
86+
t.Error("incorrect state")
87+
}
7088

7189
// three errors opens the breaker
7290
for i := 0; i < 3; i++ {
@@ -76,6 +94,9 @@ func TestBreakerStateTransitions(t *testing.T) {
7694
}
7795

7896
// breaker is open
97+
if breaker.GetState() != Open {
98+
t.Error("incorrect state")
99+
}
79100
for i := 0; i < 5; i++ {
80101
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
81102
t.Error(err)
@@ -84,6 +105,9 @@ func TestBreakerStateTransitions(t *testing.T) {
84105

85106
// wait for it to half-close
86107
time.Sleep(20 * time.Millisecond)
108+
if breaker.GetState() != HalfOpen {
109+
t.Error("incorrect state")
110+
}
87111
// one success works, but is not enough to fully close
88112
if err := breaker.Run(returnsSuccess); err != nil {
89113
t.Error(err)
@@ -93,23 +117,35 @@ func TestBreakerStateTransitions(t *testing.T) {
93117
t.Error(err)
94118
}
95119
// breaker is open
120+
if breaker.GetState() != Open {
121+
t.Error("incorrect state")
122+
}
96123
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
97124
t.Error(err)
98125
}
99126

100127
// wait for it to half-close
101128
time.Sleep(20 * time.Millisecond)
129+
if breaker.GetState() != HalfOpen {
130+
t.Error("incorrect state")
131+
}
102132
// two successes is enough to close it for good
103133
for i := 0; i < 2; i++ {
104134
if err := breaker.Run(returnsSuccess); err != nil {
105135
t.Error(err)
106136
}
107137
}
138+
if breaker.GetState() != Closed {
139+
t.Error("incorrect state")
140+
}
108141
// error works
109142
if err := breaker.Run(returnsError); err != errSomeError {
110143
t.Error(err)
111144
}
112145
// breaker is still closed
146+
if breaker.GetState() != Closed {
147+
t.Error("incorrect state")
148+
}
113149
if err := breaker.Run(returnsSuccess); err != nil {
114150
t.Error(err)
115151
}

0 commit comments

Comments
 (0)