circuitry

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2023 License: Apache-2.0 Imports: 4 Imported by: 0

README

circuitry - Distributed Circuit Breakers for Go

Credits

Much of the design of this library was informed by github.com/sony/go-breaker. The significant differences are as follows:

  • Representation of the internal state of the circuit breaker to allow it to be stored/retrieved

  • Naming of some constants to make it clearer what they are

  • Interfaces for storing the state remotely to create a distributed circuit breaker

  • A single interface rather than two interfaces (e.g., instead of a CircuitBreaker and TwoStepCircuitBreaker, one gets a single interface)

  • Default implementations for many things

  • Options Function style configuration

Documentation

Overview

Package circuitry provides a framework for building distribuited Circuit Breakers. It's primarily designed to be able to synchronize state with a backend so that a distributed system can have one Circuit Breaker for any piece it cares to implement that for.

For example, in a system calling any number of other systems, it can be beneficial to the health of the first system to implement a circuit breaker around calls to the other dependencies they have. If you have N dependencies on other systems, you could have N circuit breakers that would work across all of the instances of your system to protect it as a whole. If instead you had in memory only circuit breakers, each instance of that system would have to individually reach your failure threshold to trip the breaker before all instances have stopped talking to it. That could affect availability and performance for your users. Alternatively, you might have ot set the threshold artificially low and risk the breaker opening when it didn't need to.

circuitry aims to provide an excellent distributed circuit breaker pattern as well as supportive libraries and a handful of curated backends. For example, circuitry provides interfaces for structured logging libraries as well as implementations for logrus and slog. Likewise, it allows for metrics to be gathered from it by providing interfaces for users to specify their sink. Finally, it has a backend interface and provides implementations in Redis and DynamoDB for production usage and for reference.

Usage

The main way to interact with circuitry is to create a CircuitBreakerFactory. This looks like:

import (
	"log/slog"
	"time"

	"github.com/sigmavirus24/circuitry"
)

logger := slog.Default()
settings, err := circuitry.NewFactorySettings(
	circuitry.WithDefaultTripFunc(),
	circuitry.WithDefaultNameFunc(),
	circuitry.WithFailureCountThreshold(15),
	circuitry.WithAllowAfter(15 * time.Minute),
	circuitry.WithCyclicClearAfter(1 * time.Hour),
)
if err != nil {
	logger.With("err", err).Error("could not instantiate factory settings")
}
factory := circuitry.NewCircuitBreakerFactory(settings)

Once you have a factory you can create any number of named CircuitBreakers. circuitry also allows you to include relevant context when creating a CircuitBreaker. For example, maybe you have tracing context you

Additional Resources

For additional information see also: - https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v=pandp.10)?redirectedfrom=MSDN - https://www.redhat.com/architect/circuit-breaker-architecture-pattern - https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/circuit-breaker.html

Index

Constants

View Source
const (
	// ErrSettingConflict is returned when a setting has already been set and
	// the function refuses to override it
	ErrSettingConflict = constError("setting has already been configured via an option function, refusing to override")
	// ErrCircuitBreakerAlreadyStarted is returned when a CircuitBreaker has
	// already been started
	ErrCircuitBreakerAlreadyStarted = constError("circuit breaker has already been started and is executing")
	// ErrCircuitBreakerOpen is returned when a CircuitBreaker has already
	// been tripped and is in the open state
	ErrCircuitBreakerOpen = constError("circuit breaker is open")
	// ErrTooManyRequests is returned when a CircuitBreaker is in the
	// CircuitHalfOpen state and too many requests have been made
	ErrTooManyRequests = constError("too many requests with a circuit breaker in the half-open state")
	// ErrProvisioningStorageBackend is returned when a StorageBackend
	// encounters an issue during it's creation that is not a setting conflict
	ErrProvisioningStorageBackend = constError("could not provision storage backend")
)

Variables

View Source
var (
	// ErrStorageBackendAlreadySet is returned when the StorageBackend setting
	// has alredy been configured
	ErrStorageBackendAlreadySet = newSettingsConflictError("StorageBackend")
	// ErrFallbackErrorMatcherAlreadySet is returned when the ErrorMatcher setting has
	// already been configured
	ErrFallbackErrorMatcherAlreadySet = newSettingsConflictError("FallbackErrorMatcher")
	// ErrNameFnAlreadySet is returned when the NameFn setting has already been
	// configured
	ErrNameFnAlreadySet = newSettingsConflictError("NameFn")
	// ErrStateChangeCallbackAlreadySet is returned when the
	// StateChangeCallback setting has already been configured
	ErrStateChangeCallbackAlreadySet = newSettingsConflictError("StateChangeCallback")
	// ErrFailureCountThresholdAlreadySet is returned when the
	// FailureCountThreshold setting has already been configured
	ErrFailureCountThresholdAlreadySet = newSettingsConflictError("FailureCountThreshold")
	// ErrCloseThresholdAlreadySet is returned when the
	// CloseThreshold setting has already been configured
	ErrCloseThresholdAlreadySet = newSettingsConflictError("CloseThreshold")
	// ErrWillTripCircuitAlreadySet is returned when the WillTripCircuit
	// setting has already been configured
	ErrWillTripCircuitAlreadySet = newSettingsConflictError("WillTripCircuit")
)

Functions

func DefaultNameFunc

func DefaultNameFunc(circuit string, circuitContext map[string]any) string

DefaultNameFunc is a default name implementation that simply uses the name of te circuit for the name of a given circuit breaker

func DefaultTripFunc

func DefaultTripFunc(_ string, configuredThreshold uint64, information CircuitInformation) bool

DefaultTripFunc is the default function used to determine if a CircuitBreaker is ready to trip. By default this returns true if the number of ConsecutiveFailures is greater than the FailureCountThreshold

func WrapExpectedConditionError

func WrapExpectedConditionError(err error) error

WrapExpectedConditionError wraps an error with the ExpectedConditionError type to signal that the error should not cause a failure.

Types

type CircuitBreaker

type CircuitBreaker interface {
	// Start attempts to start work protected by the CircuitBreaker
	Start(context.Context) error
	// Name returns the name of the CircuitBreaker in use
	Name() string
	// Execute takes a function that does the work to be protected by the
	// CircuitBreaker and manages calling Start and End around the work
	// function for you. It returns the result of the function, the error if
	// returned by the function, and the error if returned from storing the
	// state in the backend. The third value may also be an error if the
	// CircuitBreaker is Open.
	Execute(context.Context, WorkFn) (workResult any, workErr error, circuitErr error)
	// End updates the status of the CircuitBreaker and returns an error if
	// there is an issue updating the storage backend
	End(context.Context, error) error
	// State returns the current state of the CircuitBreaker
	State(context.Context) (CircuitState, error)
	// Information returns the current CircuitInformation representing the
	// state of the CircuitBreaker
	Information(context.Context) (CircuitInformation, error)
}

CircuitBreaker defines the interface for an implementation of a circuit breaker.

type CircuitBreakerFactory

type CircuitBreakerFactory struct {
	// contains filtered or unexported fields
}

CircuitBreakerFactory creates CircuitBreakers for a given named circuit

func NewCircuitBreakerFactory

func NewCircuitBreakerFactory(s *FactorySettings) *CircuitBreakerFactory

NewCircuitBreakerFactory builds a new CircuitBreakerFactory from FactorySettings supplied

func (*CircuitBreakerFactory) BreakerFor

func (cbf *CircuitBreakerFactory) BreakerFor(name string, circuitContext map[string]any) CircuitBreaker

BreakerFor builds a new CircuitBreaker for the given named circuit and includes the circuit breaker contxt provided. The context is passed into the naming function and can be used by custom naming functions to produce names based off of a template

type CircuitInformation

type CircuitInformation struct {
	State                CircuitState `json:"state"`
	Generation           uint64       `json:"generation"`
	ConsecutiveFailures  uint64       `json:"consecutive_failures"`
	ConsecutiveSuccesses uint64       `json:"consecutive_successes"`
	Total                uint64       `json:"total"`
	TotalFailures        uint64       `json:"total_failures"`
	TotalSuccesses       uint64       `json:"total_successes"`
	ExpiresAfter         time.Time    `json:"expires_after"`
}

CircuitInformation describes the full state of a given CircuitBreaker to be for use with a StorageBackender

func NewCircuitInformation

func NewCircuitInformation(expiredAfter time.Duration) CircuitInformation

NewCircuitInformation creates a new CircuitInformation with the state being Open and an expiration

type CircuitSpecificSettingsConflictError

type CircuitSpecificSettingsConflictError struct {
	SettingsConflictError
	CircuitName string
}

CircuitSpecificSettingsConflictError represents a conflict for a FactorySettings for something that is specific to a given CircuitBreaker

func (CircuitSpecificSettingsConflictError) Error

func (CircuitSpecificSettingsConflictError) Unwrap

type CircuitState

type CircuitState uint32

CircuitState describes the states of a given CircuitBreaker

const (
	// CircuitClosed describes a closed CircuitBreaker
	CircuitClosed CircuitState = iota
	// CircuitOpen describes an open CircuitBreaker
	CircuitOpen
	// CircuitHalfOpen describes a half-open CircuitBreaker
	CircuitHalfOpen
)

func (CircuitState) String

func (cs CircuitState) String() string

type ExecutionStatus

type ExecutionStatus uint32

ExecutionStatus describes the status of a given execution

const (
	// ExecutionSucceeded describes a successful execution
	ExecutionSucceeded ExecutionStatus = iota
	// ExecutionFailed describes a failed execution
	ExecutionFailed
)

func DefaultErrorMatcher

func DefaultErrorMatcher(err error) ExecutionStatus

DefaultErrorMatcher is a default implementation of ExpectedErrorMatcher and assumes all errors are failed executions

func (ExecutionStatus) String

func (es ExecutionStatus) String() string

type ExpectedConditionError

type ExpectedConditionError struct {
	// contains filtered or unexported fields
}

ExpectedConditionError is provided so users can intentionally wrap an error they do not want to count as a failure without having to create additional error matchers

func (*ExpectedConditionError) Error

func (e *ExpectedConditionError) Error() string

func (*ExpectedConditionError) IsExpected

func (e *ExpectedConditionError) IsExpected() bool

IsExpected indicates that this is an expected error condition

func (*ExpectedConditionError) Unwrap

func (e *ExpectedConditionError) Unwrap() error

type ExpectedErrorMatcherFunc

type ExpectedErrorMatcherFunc func(err error) ExecutionStatus

ExpectedErrorMatcherFunc defines the signature of a function that allows the user to define when an error is expected and the execution should not be considered to be failed

type FactorySettings

type FactorySettings struct {
	StorageBackend              StorageBackender
	NameFn                      NameFunc
	FallbackErrorMatcher        ExpectedErrorMatcherFunc
	CircuitSpecificErrorMatcher map[string]ExpectedErrorMatcherFunc
	FailureCountThreshold       uint64
	CloseThreshold              uint64
	AllowAfter                  time.Duration
	CyclicClearAfter            time.Duration
	StateChangeCallback         StateChangeFunc
	WillTripCircuit             WillTripFunc
}

FactorySettings contains information for configuring a CircuitBreakerFactory and any CircuitBreaker it creates. The settings are:

  • `StorageBackend` which allows for remote storage of circuit breaker state
  • `NameFn` which allows the CircuitBreakerFactory to dynamically generate names
  • `FallbackErrorMatcher` which provides for a default error matcher if one isn't found for a specific CircuitBreaker by name
  • `CircuitSpecificErrorMatcher` which provides a way for different CircuitBreakers to have specific error matchers
  • `FailureCountThreshold` defines the threshold after which a CircuitBreaker transitions to the CircuitOpen CircuitState
  • `CloseThreshold` defines the number of requests after which a CircuitBreaker in the CircuitHalfOpen state returns to the CircuitClosed state.
  • `AllowAfter` defines the time after which a CircuitBreaker in the `CircuitOpen` CircuitState transitions to the `CircuitHalfOpen` CircuitState. If not specified, the default is 60 seconds.
  • `CyclicClearAfter` defines the time after which a CircuitBreaker resets its internal counts. If not specified, the CircuitBreaker never resets its internal counts.
  • `StateChangeCallback` provides a way to learn when the CircuitBreaker state is changing.
  • `WillTripCircuit` provides a way to customize whether the CircuitBreaker will trip beyond the failure counts configured

func NewFactorySettings

func NewFactorySettings(opts ...SettingsOption) (*FactorySettings, error)

NewFactorySettings constructs a Setings struct with the provided options

func (*FactorySettings) GenerateName

func (s *FactorySettings) GenerateName(circuit string, circuitContext map[string]any) string

GenerateName builds a name for a CircuitBreaker

type IsExpectedErrorer

type IsExpectedErrorer interface {
	IsExpected() bool
}

IsExpectedErrorer defines an interface that one can use when defining their own concrete error types. It allows users to add an IsExpected() method to their function to signal to cicuitry that the error is one that should not count as a failure.

type NameFunc

type NameFunc func(circuit string, circuitContext map[string]any) string

NameFunc defines the signature of the function used to generate names for circuits

type SettingsConflictError

type SettingsConflictError struct {
	FactorySettingsName string
	// contains filtered or unexported fields
}

SettingsConflictError contains the FactorySettingsName in the error and provides an overarching type for all errors resulting from a conflict

func (SettingsConflictError) Error

func (sce SettingsConflictError) Error() string

func (SettingsConflictError) Unwrap

func (sce SettingsConflictError) Unwrap() error

type SettingsOption

type SettingsOption func(s *FactorySettings) error

SettingsOption configures an option on a setting

func WithAllowAfter

func WithAllowAfter(duration time.Duration) SettingsOption

WithAllowAfter configures the AllowAfter setting and always overrides it.

func WithCircuitSpecificErrorMatcher

func WithCircuitSpecificErrorMatcher(circuitName string, matcher ExpectedErrorMatcherFunc) SettingsOption

WithCircuitSpecificErrorMatcher sets a specific error matcher for a given circuit with the value provided

func WithCloseThreshold

func WithCloseThreshold(threshold uint64) SettingsOption

WithCloseThreshold configures the number of consecutive successful requests needed for a CircuitHalfOpen to transition to CircuitClosed

func WithCyclicClearAfter

func WithCyclicClearAfter(duration time.Duration) SettingsOption

WithCyclicClearAfter configures the CyclicClearAfter setting and always overrides it.

func WithDefaultFallbackErrorMatcher

func WithDefaultFallbackErrorMatcher() SettingsOption

WithDefaultFallbackErrorMatcher configures the ErrorMatcher setting a the default value, DefaultErrorMatcher

func WithDefaultNameFunc

func WithDefaultNameFunc() SettingsOption

WithDefaultNameFunc configure the Name setting a the defualt value, DefaultNameFunc

func WithDefaultTripFunc

func WithDefaultTripFunc() SettingsOption

WithDefaultTripFunc sets the WillTripCircuit setting to the default function `DefaultTripFunc`

func WithFailureCountThreshold

func WithFailureCountThreshold(threshold uint64) SettingsOption

WithFailureCountThreshold configures the threshold where the circuit breaker trips to closed

func WithFallbackErrorMatcher

func WithFallbackErrorMatcher(matcher ExpectedErrorMatcherFunc) SettingsOption

WithFallbackErrorMatcher configures the ErrorMatcher with the provided matcher

func WithNameFunc

func WithNameFunc(namefn NameFunc) SettingsOption

WithNameFunc configures the Name setting with the provided NameFunc

func WithStateChangeCallback

func WithStateChangeCallback(cb StateChangeFunc) SettingsOption

WithStateChangeCallback configures the StateChangeCallback setting with the provided StateChangeFunc if it's not already set

func WithStorageBackend

func WithStorageBackend(backend StorageBackender) SettingsOption

WithStorageBackend allows the user to specify a StorageBackender implementation for CircuitBreakers

func WithTripFunc

func WithTripFunc(tf WillTripFunc) SettingsOption

WithTripFunc configures the WillTripCircuit setting to have custom logic for when a CircuitBreaker trips open

type StateChangeFunc

type StateChangeFunc func(name string, circuitContext map[string]any, from, to CircuitState)

StateChangeFunc defines the signature of a function that allows the user to define custome logic around the changing of CircuitBreaker state.

type StorageBackender

type StorageBackender interface {
	// Store the circuit information for the given string name
	Store(context.Context, string, CircuitInformation) error
	// Retrieve the circuit information
	Retrieve(context.Context, string) (CircuitInformation, error)
	// Lock provides a lock for the given name to allow for atomicity via the
	// backend
	Lock(context.Context, string) (sync.Locker, error)
}

StorageBackender defines the contract expected of a Storage Backend for the cicruit breaker in use.

type WillTripFunc

type WillTripFunc func(name string, configuredThreshold uint64, information CircuitInformation) bool

WillTripFunc defines the signature of a function that allows the user to define when a CircuitBreaker may trip. It accepts the name of the CircuitBreaker, the value of the FailureCountThreshold from the FactorySetings, and the information about the state of the circuit. It returns `true` if the CircuitBreaker should transition to `CircuitOpen`

type WorkFn

type WorkFn func() (any, error)

WorkFn defines the allowed interface of a function that can be passed to Execute

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL