Skip to content

Commit

Permalink
refactor: do not allow zero valued circuits
Browse files Browse the repository at this point in the history
This should make the code less surprising: calling a nil function
variable panics, so calling a zero value circuit is analogous to that.
  • Loading branch information
costela committed Oct 11, 2023
1 parent 6ddd372 commit f1e1e09
Show file tree
Hide file tree
Showing 3 changed files with 6 additions and 16 deletions.
2 changes: 1 addition & 1 deletion breaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type EWMABreaker struct {
// higher sample counts to avoid opening up on small hiccups.
//
// The failureThreshold is the failure rate above which the breaker should open (0.0-1.0).
func NewEWMABreaker(sampleCount int, failureThreshold float64) *EWMABreaker {
func NewEWMABreaker(sampleCount uint, failureThreshold float64) *EWMABreaker {
e := &EWMABreaker{
// https://en.wikipedia.org/wiki/Exponential_smoothing
decay: 2 / (float64(sampleCount)/2 + 1),
Expand Down
10 changes: 3 additions & 7 deletions hoglet.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// Circuit wraps a function and behaves like a simple circuit and breaker: it opens when the wrapped function fails and
// stops calling the wrapped function until it closes again, returning [ErrCircuitOpen] in the meantime.
//
// A zero circuit will not panic, but wraps a noop. Use [NewCircuit] instead.
// A zero Circuit will panic, analogous to calling a nil function variable. Initialize with [NewCircuit].
type Circuit[IN, OUT any] struct {
f BreakableFunc[IN, OUT]
breaker Breaker
Expand Down Expand Up @@ -47,8 +47,8 @@ type Breaker interface {
// BreakableFunc is the type of the function wrapped by a Breaker.
type BreakableFunc[IN, OUT any] func(context.Context, IN) (OUT, error)

// NewCircuit instantiates a new circuit breaker that wraps the given function. See [Circuit.Call] for calling semantics.
// A Circuit with a nil breaker will never open.
// NewCircuit instantiates a new [Circuit] that wraps the provided function. See [Circuit.Call] for calling semantics.
// A Circuit with a nil breaker is a noop wrapper around the provided function and will never open.
func NewCircuit[IN, OUT any](f BreakableFunc[IN, OUT], breaker Breaker, opts ...Option) *Circuit[IN, OUT] {
b := &Circuit[IN, OUT]{
f: f,
Expand Down Expand Up @@ -126,10 +126,6 @@ func (c *Circuit[IN, OUT]) setOpenedAt(i int64) {
//
// Panics are observed as failures, but are not recovered (i.e.: they are "repanicked" instead).
func (c *Circuit[IN, OUT]) Call(ctx context.Context, in IN) (out OUT, err error) {
if c.f == nil {
return out, nil
}

obs := c.observerForCall()
if obs == nil {
return out, ErrCircuitOpen
Expand Down
10 changes: 2 additions & 8 deletions hoglet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func noop(ctx context.Context, in noopIn) (struct{}, error) {

func BenchmarkHoglet_Do_EWMA(b *testing.B) {
breaker := NewCircuit(
func(context.Context, struct{}) (struct{}, error) { return struct{}{}, nil },
func(context.Context, struct{}) (out struct{}, err error) { return },
NewEWMABreaker(10, 0.9),
)

Expand All @@ -52,7 +52,7 @@ func BenchmarkHoglet_Do_EWMA(b *testing.B) {

func BenchmarkHoglet_Do_SlidingWindow(b *testing.B) {
breaker := NewCircuit(
func(context.Context, struct{}) (struct{}, error) { return struct{}{}, nil },
func(context.Context, struct{}) (out struct{}, err error) { return },
NewSlidingWindowBreaker(10*time.Second, 0.9),
)

Expand All @@ -68,12 +68,6 @@ func BenchmarkHoglet_Do_SlidingWindow(b *testing.B) {
})
}

func TestBreaker_zero_value_does_not_panic(t *testing.T) {
b := &Circuit[struct{}, struct{}]{}
_, err := b.Call(context.Background(), struct{}{})
assert.NoError(t, err)
}

func TestBreaker_nil_breaker_does_not_open(t *testing.T) {
b := NewCircuit(noop, nil)
_, err := b.Call(context.Background(), noopInFailure)
Expand Down

0 comments on commit f1e1e09

Please sign in to comment.