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
Public Class Methods
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
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
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
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
Check whether Faulty
was disabled with {#disable!}
@return [Boolean] True if disabled
# File lib/faulty.rb, line 155 def disabled? @disabled == true end
Re-enable Faulty
if disabled with {#disable!}
@return [void]
# File lib/faulty.rb, line 148 def enable! @disabled = false end
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
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
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
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 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
The current Faulty
version
# File lib/faulty/version.rb, line 5 def self.version Gem::Version.new('0.7.2') end
Public Instance Methods
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
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
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
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
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
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
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
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
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
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
# File lib/faulty/status.rb, line 164 def defaults { state: :closed, failure_rate: 0.0, sample_size: 0, stub: false } end
# 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
# File lib/faulty/status.rb, line 160 def required %i[state failure_rate sample_size options stub] end