Skip to content

Commit

Permalink
Support golang 1.18 or higher and type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
kuzuha committed Apr 19, 2022
1 parent 723c821 commit 4e33c0d
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 167 deletions.
125 changes: 63 additions & 62 deletions breaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ const (
)

// State represents the internal state of CB.
type State string
type State[T any] string

// State constants.
const (
StateClosed State = "closed"
StateOpen State = "open"
StateHalfOpen State = "half-open"
StateClosed State[any] = "closed"
StateOpen State[any] = "open"
StateHalfOpen State[any] = "half-open"
)

// DefaultOpenBackOff returns defaultly used BackOff.
Expand Down Expand Up @@ -77,7 +77,7 @@ func (c *Counters) incrementFailures() {
}

// StateChangeHook is a function which will be invoked when the state is changed.
type StateChangeHook func(oldState, newState State)
type StateChangeHook[T any] func(oldState, newState State[T])

// TripFunc is a function to determine if CircuitBreaker should open (trip) or
// not. TripFunc is called when cb.Fail was called and the state was
Expand Down Expand Up @@ -149,7 +149,7 @@ func MarkAsSuccess(err error) error {
}

// Options holds CircuitBreaker configuration options.
type options struct {
type options[T any] struct {
// Clock to be used by CircuitBreaker. If nil, real-time clock is
// used.
clock clock.Clock
Expand Down Expand Up @@ -191,7 +191,7 @@ type options struct {
shouldTrip TripFunc

// OnStateChange is a function which will be invoked when the state is changed.
onStateChange StateChangeHook
onStateChange StateChangeHook[T]

// FailOnContextCancel controls if CircuitBreaker mark an error when the
// passed context.Done() is context.Canceled as a fail.
Expand All @@ -203,97 +203,97 @@ type options struct {
}

// CircuitBreaker provides circuit breaker pattern.
type CircuitBreaker struct {
type CircuitBreaker[T any] struct {
clock clock.Clock
interval time.Duration
halfOpenMaxSuccesses int64
openBackOff backoff.BackOff
shouldTrip TripFunc
onStateChange StateChangeHook
onStateChange StateChangeHook[T]
failOnContextCancel bool
failOnContextDeadline bool

mu sync.RWMutex
state state
state state[T]
cnt Counters
}

type fnApplyOptions func(*options)
type fnApplyOptions[T any] func(*options[T])

// BreakerOption interface for applying configuration in the constructor
type BreakerOption interface {
apply(*options)
type BreakerOption[T any] interface {
apply(*options[T])
}

func (f fnApplyOptions) apply(options *options) {
func (f fnApplyOptions[T]) apply(options *options[T]) {
f(options)
}

// WithTripFunc Set the function for counter
func WithTripFunc(tripFunc TripFunc) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithTripFunc[T any](tripFunc TripFunc) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.shouldTrip = tripFunc
})
}

// WithClock Set the clock
func WithClock(clock clock.Clock) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithClock[T any](clock clock.Clock) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.clock = clock
})
}

// WithOpenTimeoutBackOff Set the time backoff
func WithOpenTimeoutBackOff(backoff backoff.BackOff) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithOpenTimeoutBackOff[T any](backoff backoff.BackOff) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.openBackOff = backoff
})
}

// WithOpenTimeout Set the timeout of the circuit breaker
func WithOpenTimeout(timeout time.Duration) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithOpenTimeout[T any](timeout time.Duration) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.openTimeout = timeout
})
}

// WithHalfOpenMaxSuccesses Set the number of half open successes
func WithHalfOpenMaxSuccesses(maxSuccesses int64) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithHalfOpenMaxSuccesses[T any](maxSuccesses int64) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.halfOpenMaxSuccesses = maxSuccesses
})
}

// WithCounterResetInterval Set the interval of the circuit breaker, which is the cyclic time period to reset the internal counters
func WithCounterResetInterval(interval time.Duration) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithCounterResetInterval[T any](interval time.Duration) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.interval = interval
})
}

// WithFailOnContextCancel Set if the context should fail on cancel
func WithFailOnContextCancel(failOnContextCancel bool) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithFailOnContextCancel[T any](failOnContextCancel bool) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.failOnContextCancel = failOnContextCancel
})
}

// WithFailOnContextDeadline Set if the context should fail on deadline
func WithFailOnContextDeadline(failOnContextDeadline bool) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithFailOnContextDeadline[T any](failOnContextDeadline bool) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.failOnContextDeadline = failOnContextDeadline
})
}

// WithOnStateChangeHookFn set a hook function that trigger if the condition of the StateChangeHook is true
func WithOnStateChangeHookFn(hookFn StateChangeHook) BreakerOption {
return fnApplyOptions(func(options *options) {
func WithOnStateChangeHookFn[T any](hookFn StateChangeHook[T]) BreakerOption[T] {
return fnApplyOptions[T](func(options *options[T]) {
options.onStateChange = hookFn
})
}

func defaultOptions() *options {
return &options{
func defaultOptions[T any]() *options[T] {
return &options[T]{
shouldTrip: DefaultTripFunc,
clock: clock.New(),
openBackOff: DefaultOpenBackOff(),
Expand Down Expand Up @@ -324,8 +324,8 @@ func defaultOptions() *options {
// )
//
// The default options are described in the defaultOptions function
func New(opts ...BreakerOption) *CircuitBreaker {
cbOptions := defaultOptions()
func New[T any](opts ...BreakerOption[T]) *CircuitBreaker[T] {
cbOptions := defaultOptions[T]()

for _, opt := range opts {
opt.apply(cbOptions)
Expand All @@ -335,7 +335,7 @@ func New(opts ...BreakerOption) *CircuitBreaker {
cbOptions.openBackOff = backoff.NewConstantBackOff(cbOptions.openTimeout)
}

cb := &CircuitBreaker{
cb := &CircuitBreaker[T]{
shouldTrip: cbOptions.shouldTrip,
onStateChange: cbOptions.onStateChange,
clock: cbOptions.clock,
Expand All @@ -345,12 +345,12 @@ func New(opts ...BreakerOption) *CircuitBreaker {
failOnContextCancel: cbOptions.failOnContextCancel,
failOnContextDeadline: cbOptions.failOnContextDeadline,
}
cb.setState(&stateClosed{})
cb.setState(&stateClosed[T]{})
return cb
}

// An Operation is executed by Do().
type Operation func() (interface{}, error)
type Operation[T any] func() (T, error)

// Do executes the Operation o and returns the return values if
// cb.Ready() is true. If not ready, cb doesn't execute f and returns
Expand All @@ -372,33 +372,34 @@ type Operation func() (interface{}, error)
// If given Options' FailOnContextDeadline is false (default), cb.Do
// doesn't mark the Operation's error as a failure if ctx.Err() returns
// context.DeadlineExceeded.
func (cb *CircuitBreaker) Do(ctx context.Context, o Operation) (interface{}, error) {
func (cb *CircuitBreaker[T]) Do(ctx context.Context, o Operation[T]) (T, error) {
var ret T
if !cb.Ready() {
return nil, ErrOpen
return ret, ErrOpen
}
result, err := o()
return result, cb.Done(ctx, err)
ret, err := o()
return ret, cb.Done(ctx, err)
}

// Ready reports if cb is ready to execute an operation. Ready does not give
// any change to cb.
func (cb *CircuitBreaker) Ready() bool {
func (cb *CircuitBreaker[T]) Ready() bool {
cb.mu.RLock()
defer cb.mu.RUnlock()
return cb.state.ready(cb)
}

// Success signals that an execution of operation has been completed
// successfully to cb.
func (cb *CircuitBreaker) Success() {
func (cb *CircuitBreaker[T]) Success() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.cnt.incrementSuccesses()
cb.state.onSuccess(cb)
}

// Fail signals that an execution of operation has been failed to cb.
func (cb *CircuitBreaker) Fail() {
func (cb *CircuitBreaker[T]) Fail() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.cnt.incrementFailures()
Expand All @@ -409,7 +410,7 @@ func (cb *CircuitBreaker) Fail() {
// and ctx is done with context.Canceled error, no Fail() called. Similarly, if
// FailOnContextDeadline is false and ctx is done with context.DeadlineExceeded
// error, no Fail() called.
func (cb *CircuitBreaker) FailWithContext(ctx context.Context) {
func (cb *CircuitBreaker[T]) FailWithContext(ctx context.Context) {
if ctxErr := ctx.Err(); ctxErr != nil {
if ctxErr == context.Canceled && !cb.failOnContextCancel {
return
Expand All @@ -425,7 +426,7 @@ func (cb *CircuitBreaker) FailWithContext(ctx context.Context) {
// Done calls Success and returns nil. If err is a SuccessMarkableError or
// IgnorableError, Done returns wrapped error. Otherwise, Done calls
// FailWithContext internally.
func (cb *CircuitBreaker) Done(ctx context.Context, err error) error {
func (cb *CircuitBreaker[T]) Done(ctx context.Context, err error) error {
if err == nil {
cb.Success()
return nil
Expand All @@ -445,49 +446,49 @@ func (cb *CircuitBreaker) Done(ctx context.Context, err error) error {
}

// State reports the curent State of cb.
func (cb *CircuitBreaker) State() State {
func (cb *CircuitBreaker[T]) State() State[T] {
cb.mu.Lock()
defer cb.mu.Unlock()
return cb.state.State()
}

// Counters returns internal counters. If current status is not
// StateClosed, returns zero value.
func (cb *CircuitBreaker) Counters() Counters {
func (cb *CircuitBreaker[T]) Counters() Counters {
cb.mu.Lock()
defer cb.mu.Unlock()
return cb.cnt
}

// Reset resets cb's state with StateClosed.
func (cb *CircuitBreaker) Reset() {
func (cb *CircuitBreaker[T]) Reset() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.cnt.reset()
cb.setState(&stateClosed{})
cb.setState(&stateClosed[T]{})
}

// SetState set state of cb to st.
func (cb *CircuitBreaker) SetState(st State) {
func (cb *CircuitBreaker[T]) SetState(st State[T]) {
switch st {
case StateClosed:
cb.setStateWithLock(&stateClosed{})
case StateOpen:
cb.setStateWithLock(&stateOpen{})
case StateHalfOpen:
cb.setStateWithLock(&stateHalfOpen{})
case State[T](StateClosed):
cb.setStateWithLock(&stateClosed[T]{})
case State[T](StateOpen):
cb.setStateWithLock(&stateOpen[T]{})
case State[T](StateHalfOpen):
cb.setStateWithLock(&stateHalfOpen[T]{})
default:
panic("undefined state")
}
}

func (cb *CircuitBreaker) setStateWithLock(s state) {
func (cb *CircuitBreaker[T]) setStateWithLock(s state[T]) {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.setState(s)
}

func (cb *CircuitBreaker) setState(s state) {
func (cb *CircuitBreaker[T]) setState(s state[T]) {
if cb.state != nil {
cb.state.onExit(cb)
}
Expand All @@ -497,7 +498,7 @@ func (cb *CircuitBreaker) setState(s state) {
cb.handleOnStateChange(from, s)
}

func (cb *CircuitBreaker) handleOnStateChange(from, to state) {
func (cb *CircuitBreaker[T]) handleOnStateChange(from, to state[T]) {
if from == nil || cb.onStateChange == nil {
return
}
Expand Down
Loading

0 comments on commit 4e33c0d

Please sign in to comment.