31
31
package circuit
32
32
33
33
import (
34
+ "container/ring"
34
35
"context"
35
36
"errors"
36
37
"sync"
@@ -75,12 +76,14 @@ const (
75
76
var (
76
77
defaultInitialBackOffInterval = 500 * time .Millisecond
77
78
defaultBackoffMaxElapsedTime = 0 * time .Second
79
+ defaultErrorHistoryDepth = 10
78
80
)
79
81
80
82
// Error codes returned by Call
81
83
var (
82
- ErrBreakerOpen = errors .New ("breaker open" )
83
- ErrBreakerTimeout = errors .New ("breaker time out" )
84
+ ErrBreakerOpen = errors .New ("breaker open" )
85
+ ErrBreakerTimeout = errors .New ("breaker time out" )
86
+ ErrBreakerNoErrorRecorded = errors .New ("no error in breaker history" )
84
87
)
85
88
86
89
// TripFunc is a function called by a Breaker's Fail() function and determines whether
@@ -115,15 +118,19 @@ type Breaker struct {
115
118
eventReceivers []chan BreakerEvent
116
119
listeners []chan ListenerEvent
117
120
backoffLock sync.Mutex
121
+
122
+ //ring buffer for last N errors
123
+ errorsBuffer * ring.Ring
118
124
}
119
125
120
126
// Options holds breaker configuration options.
121
127
type Options struct {
122
- BackOff backoff.BackOff
123
- Clock clock.Clock
124
- ShouldTrip TripFunc
125
- WindowTime time.Duration
126
- WindowBuckets int
128
+ BackOff backoff.BackOff
129
+ Clock clock.Clock
130
+ ShouldTrip TripFunc
131
+ WindowTime time.Duration
132
+ WindowBuckets int
133
+ ErrorHistoryDepth int
127
134
}
128
135
129
136
// NewBreakerWithOptions creates a base breaker with a specified backoff, clock and TripFunc
@@ -153,12 +160,17 @@ func NewBreakerWithOptions(options *Options) *Breaker {
153
160
options .WindowBuckets = DefaultWindowBuckets
154
161
}
155
162
163
+ if options .ErrorHistoryDepth <= 0 {
164
+ options .ErrorHistoryDepth = defaultErrorHistoryDepth
165
+ }
166
+
156
167
return & Breaker {
157
- BackOff : options .BackOff ,
158
- Clock : options .Clock ,
159
- ShouldTrip : options .ShouldTrip ,
160
- nextBackOff : options .BackOff .NextBackOff (),
161
- counts : newWindow (options .WindowTime , options .WindowBuckets ),
168
+ BackOff : options .BackOff ,
169
+ Clock : options .Clock ,
170
+ ShouldTrip : options .ShouldTrip ,
171
+ nextBackOff : options .BackOff .NextBackOff (),
172
+ counts : newWindow (options .WindowTime , options .WindowBuckets ),
173
+ errorsBuffer : ring .New (options .ErrorHistoryDepth ),
162
174
}
163
175
}
164
176
@@ -293,6 +305,35 @@ func (cb *Breaker) Fail() {
293
305
}
294
306
}
295
307
308
+ // FailWithError is the same as Fail, but keeps history of errors in internal ring buffer
309
+ func (cb * Breaker ) FailWithError (err error ) {
310
+ cb .errorsBuffer = cb .errorsBuffer .Next ()
311
+ cb .errorsBuffer .Value = err
312
+ cb .Fail ()
313
+ }
314
+
315
+ // LastError returns last error from internal buffer
316
+ func (cb * Breaker ) LastError () error {
317
+ if cb .errorsBuffer .Value == nil {
318
+ return ErrBreakerNoErrorRecorded
319
+ }
320
+ return cb .errorsBuffer .Value .(error )
321
+ }
322
+
323
+ // Errors returns all errors from internal buffer
324
+ func (cb * Breaker ) Errors () (errors []error ) {
325
+ // reserve capacity to move last error to the end of slice without realloc
326
+ errors = make ([]error , 0 , cb .errorsBuffer .Len ()+ 1 )
327
+ cb .errorsBuffer .Do (func (x interface {}) {
328
+ if x != nil {
329
+ errors = append (errors , x .(error ))
330
+ }
331
+ })
332
+ // move last error to the end
333
+ errors = append (errors [1 :], errors [0 ])
334
+ return errors
335
+ }
336
+
296
337
// Success is used to indicate a success condition the Breaker should record. If
297
338
// the success was triggered by a retry attempt, the breaker will be Reset().
298
339
func (cb * Breaker ) Success () {
@@ -302,7 +343,9 @@ func (cb *Breaker) Success() {
302
343
cb .backoffLock .Unlock ()
303
344
304
345
state := cb .state ()
305
- if state == halfopen {
346
+ // if state was halfopen and it's successful request this state will be `open`.
347
+ // due to cb.halfOpens is 1 at this point (request grouping)
348
+ if state == halfopen || state == open {
306
349
cb .Reset ()
307
350
}
308
351
atomic .StoreInt64 (& cb .consecFailures , 0 )
@@ -362,7 +405,7 @@ func (cb *Breaker) CallContext(ctx context.Context, circuit func() error, timeou
362
405
363
406
if err != nil {
364
407
if ctx .Err () != context .Canceled {
365
- cb .Fail ( )
408
+ cb .FailWithError ( err )
366
409
}
367
410
return err
368
411
}
0 commit comments