Skip to content

Commit 9d2405c

Browse files
authored
Merge pull request #42 from matope/add-callcontext-on-breaker
Add Breaker.CallContext to handle context.Canceled
2 parents af95830 + 54dfa4d commit 9d2405c

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

circuitbreaker.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
package circuit
3232

3333
import (
34+
"context"
3435
"errors"
3536
"sync"
3637
"sync/atomic"
@@ -329,6 +330,12 @@ func (cb *Breaker) Ready() bool {
329330
// whenever the function returns an error. If the called function takes longer
330331
// than timeout to run, a failure will be recorded.
331332
func (cb *Breaker) Call(circuit func() error, timeout time.Duration) error {
333+
return cb.CallContext(context.Background(), circuit, timeout)
334+
}
335+
336+
// CallContext is same as Call but if the ctx is canceled after the circuit returned an error,
337+
// the error will not be marked as a failure because the call was canceled intentionally.
338+
func (cb *Breaker) CallContext(ctx context.Context, circuit func() error, timeout time.Duration) error {
332339
var err error
333340

334341
if !cb.Ready() {
@@ -353,7 +360,9 @@ func (cb *Breaker) Call(circuit func() error, timeout time.Duration) error {
353360
}
354361

355362
if err != nil {
356-
cb.Fail()
363+
if ctx.Err() != context.Canceled {
364+
cb.Fail()
365+
}
357366
return err
358367
}
359368

circuitbreaker_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package circuit
22

33
import (
4+
"context"
45
"fmt"
56
"sync/atomic"
67
"testing"
@@ -252,6 +253,42 @@ func TestThresholdBreakerCalling(t *testing.T) {
252253
}
253254
}
254255

256+
func TestThresholdBreakerCallingContext(t *testing.T) {
257+
circuit := func() error {
258+
return fmt.Errorf("error")
259+
}
260+
261+
cb := NewThresholdBreaker(2)
262+
ctx, cancel := context.WithCancel(context.Background())
263+
264+
err := cb.CallContext(ctx, circuit, 0) // First failure
265+
if err == nil {
266+
t.Fatal("expected threshold breaker to error")
267+
}
268+
if cb.Tripped() {
269+
t.Fatal("expected threshold breaker to be open")
270+
}
271+
272+
// Cancel the next Call.
273+
cancel()
274+
275+
err = cb.CallContext(ctx, circuit, 0) // Second failure but it's canceled
276+
if err == nil {
277+
t.Fatal("expected threshold breaker to error")
278+
}
279+
if cb.Tripped() {
280+
t.Fatal("expected threshold breaker to be open")
281+
}
282+
283+
err = cb.CallContext(context.Background(), circuit, 0) // Thirt failure trips
284+
if err == nil {
285+
t.Fatal("expected threshold breaker to error")
286+
}
287+
if !cb.Tripped() {
288+
t.Fatal("expected threshold breaker to be tripped")
289+
}
290+
}
291+
255292
func TestThresholdBreakerResets(t *testing.T) {
256293
called := 0
257294
success := false

0 commit comments

Comments
 (0)