Skip to content

Commit 2a81223

Browse files
authored
Merge pull request #45 from eapache/breaker/get-state
Add breaker.GetState() method to inspect state
2 parents 808c606 + 3ae3046 commit 2a81223

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)