class Faulty

The {Faulty} class has class-level methods for global state or can be instantiated to create an independent configuration.

If you are using global state, call {Faulty#init} during your application's initialization. This is the simplest way to use {Faulty}. If you prefer, you can also call {Faulty.new} to create independent {Faulty} instances.

Constants

Options

Options for {Faulty}

@!attribute [r] cache

@see Cache::AutoWire
@return [Cache::Interface] A cache backend if you want
  to use Faulty's cache support. Automatically wrapped in a
  {Cache::AutoWire}. Default `Cache::AutoWire.new`.

@!attribute [r] circuit_defaults

@see Circuit::Options
@return [Hash] A hash of default options to be used when creating
  new circuits. See {Circuit::Options} for a full list.

@!attribute [r] storage

@see Storage::AutoWire
@return [Storage::Interface, Array<Storage::Interface>] The storage
backend. Automatically wrapped in a {Storage::AutoWire}, so this can also
be given an array of prioritized backends. Default `Storage::AutoWire.new`.

@!attribute [r] listeners

@see Events::ListenerInterface
@return [Array] listeners Faulty event listeners

@!attribute [r] notifier

@return [Events::Notifier] A Faulty notifier. If given, listeners are
  ignored.
Status

The status of a circuit

Includes information like the state and locks. Also calculates whether a circuit can be run, or if it has failed a threshold.

@!attribute [r] state

@return [:open, :closed] The stored circuit state. This is always open
  or closed. Half-open is calculated from the current time. For that
  reason, calling state directly should be avoided. Instead use the
  status methods {#open?}, {#closed?}, and {#half_open?}.
  Default `:closed`

@!attribute [r] lock

@return [:open, :closed, nil] If the circuit is locked, the state that
  it is locked in. Default `nil`.

@!attribute [r] opened_at

@return [Integer, nil] If the circuit is open, the timestamp that it was
  opened. This is not necessarily reset when the circuit is closed.
  Default `nil`.

@!attribute [r] failure_rate

@return [Float] A number from 0 to 1 representing the percentage of
  failures for the circuit. For exmaple 0.5 represents a 50% failure rate.

@!attribute [r] sample_size

@return [Integer] The number of samples used to calculate the failure rate.

@!attribute [r] options

@return [Circuit::Options] The options for the circuit

@!attribute [r] stub

@return [Boolean] True if this status is a stub and not calculated from
  the storage backend. Used by {Storage::FaultTolerantProxy} when
  returning the status for an offline storage backend. Default `false`.

Attributes

options[R]

Public Class Methods

[](name) click to toggle source

Get an instance by name

@return [Faulty, nil] The named instance if it is registered

# File lib/faulty.rb, line 67
def [](name)
  raise UninitializedError unless @instances

  @instances[name.to_s]
end
circuit(name, **config, &block) click to toggle source

Get or create a circuit for the default instance

@raise UninitializedError If the default instance has not been created @param (see Faulty#circuit) @yield (see Faulty#circuit) @return (see Faulty#circuit)

# File lib/faulty.rb, line 111
def circuit(name, **config, &block)
  default.circuit(name, **config, &block)
end
current_time() click to toggle source

The current time

Used by Faulty wherever the current time is needed. Can be overridden for testing

@return [Time] The current time

# File lib/faulty.rb, line 128
def current_time
  Time.now.to_i
end
default() click to toggle source

Get the default instance given during {.init}

@return [Faulty, nil] The default instance if it is registered

# File lib/faulty.rb, line 57
def default
  raise UninitializedError unless @instances
  raise MissingDefaultInstanceError unless @default_instance

  self[@default_instance]
end
disable!() click to toggle source

Disable Faulty circuits

This allows circuits to run as if they were always closed. Does not disable caching.

Intended for use in tests, or to disable Faulty entirely for an environment.

@return [void]

# File lib/faulty.rb, line 141
def disable!
  @disabled = true
end
disabled?() click to toggle source

Check whether Faulty was disabled with {#disable!}

@return [Boolean] True if disabled

# File lib/faulty.rb, line 155
def disabled?
  @disabled == true
end
enable!() click to toggle source

Re-enable Faulty if disabled with {#disable!}

@return [void]

# File lib/faulty.rb, line 148
def enable!
  @disabled = false
end
init(default_name = :default, **config, &block) click to toggle source

Start the Faulty environment

This creates a global shared Faulty state for configuration and for re-using State objects.

Not thread safe, should be executed before any worker threads are spawned.

If you prefer dependency-injection instead of global state, you can skip `init` and use {Faulty.new} to pass an instance directoy to your dependencies.

@param default_name [Symbol] The name of the default instance. Can be set to `nil` to skip creating a default instance. @param config [Hash] Attributes for {Faulty::Options} @yield [Faulty::Options] For setting options in a block @return [self]

# File lib/faulty.rb, line 42
def init(default_name = :default, **config, &block)
  raise AlreadyInitializedError if @instances

  @default_instance = default_name
  @instances = Concurrent::Map.new
  register(default_name, new(**config, &block)) unless default_name.nil?
  self
rescue StandardError
  @instances = nil
  raise
end
list_circuits() click to toggle source

Get a list of all circuit names for the default instance

@return [Array<String>] The circuit names

# File lib/faulty.rb, line 118
def list_circuits
  options.storage.list
end
new(**options, &block) click to toggle source

Create a new {Faulty} instance

Note, the process of creating a new instance is not thread safe, so make sure instances are setup during your application's initialization phase.

For the most part, {Faulty} instances are independent, however for some cache and storage backends, you will need to ensure that the cache keys and circuit names don't overlap between instances. For example, if using the {Storage::Redis} storage backend, you should specify different key prefixes for each instance.

@see Options @param options [Hash] Attributes for {Options} @yield [Options] For setting options in a block

# File lib/faulty.rb, line 228
def initialize(**options, &block)
  @circuits = Concurrent::Map.new
  @options = Options.new(options, &block)
end
options() click to toggle source

Get the options for the default instance

@raise MissingDefaultInstanceError If the default instance has not been created @return [Faulty::Options]

# File lib/faulty.rb, line 101
def options
  default.options
end
register(name, instance = nil, **config, &block) click to toggle source

Register an instance to the global Faulty state

Will not replace an existing instance with the same name. Check the return value if you need to know whether the instance already existed.

@param name [Symbol] The name of the instance to register @param instance [Faulty] The instance to register. If nil, a new instance

will be created from the given options or block.

@param config [Hash] Attributes for {Faulty::Options} @yield [Faulty::Options] For setting options in a block @return [Faulty, nil] The previously-registered instance of that name if

it already existed, otherwise nil.
# File lib/faulty.rb, line 85
def register(name, instance = nil, **config, &block)
  raise UninitializedError unless @instances

  if instance
    raise ArgumentError, 'Do not give config options if an instance is given' if !config.empty? || block
  else
    instance = new(**config, &block)
  end

  @instances.put_if_absent(name.to_s, instance)
end
version() click to toggle source

The current Faulty version

# File lib/faulty/version.rb, line 5
def self.version
  Gem::Version.new('0.7.2')
end

Public Instance Methods

can_run?() click to toggle source

Whether the circuit can be run

Takes the circuit state, locks and cooldown into account

@return [Boolean] True if the circuit can be run

# File lib/faulty/status.rb, line 135
def can_run?
  return false if locked_open?

  closed? || locked_closed? || half_open?
end
circuit(name, **options, &block) click to toggle source

Create or retrieve a circuit

Within an instance, circuit instances have unique names, so if the given circuit name already exists, then the existing circuit will be returned, otherwise a new circuit will be created. If an existing circuit is returned, then the {options} param and block are ignored.

@param name [String] The name of the circuit @param options [Hash] Attributes for {Circuit::Options} @yield [Circuit::Options] For setting options in a block @return [Circuit] The new circuit or the existing circuit if it already exists

# File lib/faulty.rb, line 244
def circuit(name, **options, &block)
  name = name.to_s
  @circuits.compute_if_absent(name) do
    options = circuit_options.merge(options)
    Circuit.new(name, **options, &block)
  end
end
closed?() click to toggle source

Whether the circuit is closed

This is mutually exclusive with {#open?} and {#half_open?}

@return [Boolean] True if closed

# File lib/faulty/status.rb, line 103
def closed?
  state == :closed
end
fails_threshold?() click to toggle source

Whether the circuit fails the sample size and rate thresholds

@return [Boolean] True if the circuit fails the thresholds

# File lib/faulty/status.rb, line 144
def fails_threshold?
  return false if sample_size < options.sample_threshold

  failure_rate >= options.rate_threshold
end
half_open?() click to toggle source

Whether the circuit is half-open

This is mutually exclusive with {#open?} and {#closed?}

@return [Boolean] True if half-open

# File lib/faulty/status.rb, line 112
def half_open?
  state == :open && opened_at + options.cool_down <= Faulty.current_time
end
list_circuits() click to toggle source

Get a list of all circuit names

@return [Array<String>] The circuit names

# File lib/faulty.rb, line 255
def list_circuits
  options.storage.list
end
locked_closed?() click to toggle source

Whether the circuit is locked closed

@return [Boolean] True if locked closed

# File lib/faulty/status.rb, line 126
def locked_closed?
  lock == :closed
end
locked_open?() click to toggle source

Whether the circuit is locked open

@return [Boolean] True if locked open

# File lib/faulty/status.rb, line 119
def locked_open?
  lock == :open
end
open?() click to toggle source

Whether the circuit is open

This is mutually exclusive with {#closed?} and {#half_open?}

@return [Boolean] True if open

# File lib/faulty/status.rb, line 94
def open?
  state == :open && opened_at + options.cool_down > Faulty.current_time
end

Private Instance Methods

circuit_options() click to toggle source

Get circuit options from the {Faulty} options

@return [Hash] The circuit options

# File lib/faulty.rb, line 264
def circuit_options
  @options.to_h
    .select { |k, _v| %i[cache storage notifier].include?(k) }
    .merge(options.circuit_defaults)
end
defaults() click to toggle source
# File lib/faulty/status.rb, line 164
def defaults
  {
    state: :closed,
    failure_rate: 0.0,
    sample_size: 0,
    stub: false
  }
end
finalize() click to toggle source
# File lib/faulty/status.rb, line 152
def finalize
  raise ArgumentError, "state must be a symbol in #{self.class}::STATES" unless STATES.include?(state)
  unless lock.nil? || LOCKS.include?(lock)
    raise ArgumentError, "lock must be a symbol in #{self.class}::LOCKS or nil"
  end
  raise ArgumentError, 'opened_at is required if state is open' if state == :open && opened_at.nil?
end
required() click to toggle source
# File lib/faulty/status.rb, line 160
def required
  %i[state failure_rate sample_size options stub]
end