class Faulty::Storage::FallbackChain

An prioritized list of storage backends

If any backend fails, the next will be tried until one succeeds. This should typically be used when using a fault-prone backend such as {Storage::Redis}.

This is used by {Faulty#initialize} if the `storage` option is set to an array.

@example

# This storage will try Redis first, then fallback to memory storage
# if Redis is unavailable.
storage = Faulty::Storage::FallbackChain.new([
  Faulty::Storage::Redis.new,
  Faulty::Storage::Memory.new
])

Constants

Options

Options for {FallbackChain}

@!attribute [r] notifier

@return [Events::Notifier] A Faulty notifier

Attributes

options[R]

Public Class Methods

new(storages, **options, &block) click to toggle source

Create a new {FallbackChain} to automatically fallback to reliable storage

@param storages [Array<Storage::Interface>] An array of storage backends.

The primary storage should be specified first. If that one fails,
additional entries will be tried in sequence until one succeeds.

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

# File lib/faulty/storage/fallback_chain.rb, line 47
def initialize(storages, **options, &block)
  @storages = storages
  @options = Options.new(options, &block)
end

Public Instance Methods

close(circuit) click to toggle source

Close a circuit in the first available storage backend

@param (see Interface#close) @return (see Interface#close)

# File lib/faulty/storage/fallback_chain.rb, line 86
def close(circuit)
  send_chain(:close, circuit) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :close, error: e)
  end
end
entry(circuit, time, success) click to toggle source

Create a circuit entry in the first available storage backend

@param (see Interface#entry) @return (see Interface#entry)

# File lib/faulty/storage/fallback_chain.rb, line 56
def entry(circuit, time, success)
  send_chain(:entry, circuit, time, success) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :entry, error: e)
  end
end
fault_tolerant?() click to toggle source

This is fault tolerant if any of the available backends are fault tolerant

@param (see Interface#fault_tolerant?) @return (see Interface#fault_tolerant?)

# File lib/faulty/storage/fallback_chain.rb, line 150
def fault_tolerant?
  @storages.any?(&:fault_tolerant?)
end
history(circuit) click to toggle source

Get the history of a circuit from the first available storage backend

@param (see Interface#history) @return (see Interface#history)

# File lib/faulty/storage/fallback_chain.rb, line 130
def history(circuit)
  send_chain(:history, circuit) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :history, error: e)
  end
end
list() click to toggle source

Get the list of circuits from the first available storage backend

@param (see Interface#list) @return (see Interface#list)

# File lib/faulty/storage/fallback_chain.rb, line 140
def list
  send_chain(:list) do |e|
    options.notifier.notify(:storage_failure, action: :list, error: e)
  end
end
lock(circuit, state) click to toggle source

Lock a circuit in all storage backends

@param (see Interface#lock) @return (see Interface#lock)

# File lib/faulty/storage/fallback_chain.rb, line 96
def lock(circuit, state)
  send_all(:lock, circuit, state)
end
open(circuit, opened_at) click to toggle source

Open a circuit in the first available storage backend

@param (see Interface#open) @return (see Interface#open)

# File lib/faulty/storage/fallback_chain.rb, line 66
def open(circuit, opened_at)
  send_chain(:open, circuit, opened_at) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :open, error: e)
  end
end
reopen(circuit, opened_at, previous_opened_at) click to toggle source

Reopen a circuit in the first available storage backend

@param (see Interface#reopen) @return (see Interface#reopen)

# File lib/faulty/storage/fallback_chain.rb, line 76
def reopen(circuit, opened_at, previous_opened_at)
  send_chain(:reopen, circuit, opened_at, previous_opened_at) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :reopen, error: e)
  end
end
reset(circuit) click to toggle source

Reset a circuit in all storage backends

@param (see Interface#reset) @return (see Interface#reset)

# File lib/faulty/storage/fallback_chain.rb, line 112
def reset(circuit)
  send_all(:reset, circuit)
end
status(circuit) click to toggle source

Get the status of a circuit from the first available storage backend

@param (see Interface#status) @return (see Interface#status)

# File lib/faulty/storage/fallback_chain.rb, line 120
def status(circuit)
  send_chain(:status, circuit) do |e|
    options.notifier.notify(:storage_failure, circuit: circuit, action: :status, error: e)
  end
end
unlock(circuit) click to toggle source

Unlock a circuit in all storage backends

@param (see Interface#unlock) @return (see Interface#unlock)

# File lib/faulty/storage/fallback_chain.rb, line 104
def unlock(circuit)
  send_all(:unlock, circuit)
end

Private Instance Methods

send_all(method, *args) click to toggle source

Call a method on every backend

@param method [Symbol] The method to call @param args [Array] The arguments to send @raise [AllFailedError] AllFailedError if all backends fail @raise [PartialFailureError] PartialFailureError if some but not all

backends fail

@return [nil]

# File lib/faulty/storage/fallback_chain.rb, line 187
def send_all(method, *args)
  errors = []
  @storages.each do |s|
    begin
      s.public_send(method, *args)
    rescue StandardError => e
      errors << e
    end
  end

  if errors.empty?
    nil
  elsif errors.size < @storages.size
    raise PartialFailureError.new("#{self.class}##{method} failed for some storage backends", errors)
  else
    raise AllFailedError.new("#{self.class}##{method} failed for all storage backends", errors)
  end
end
send_chain(method, *args) { |e| ... } click to toggle source

Call a method on the backend and return the first successful result

Short-circuits, so that if a call succeeds, no additional backends are called.

@param method [Symbol] The method to call @param args [Array] The arguments to send @raise [AllFailedError] AllFailedError if all backends fail @return The return value from the first successful call

# File lib/faulty/storage/fallback_chain.rb, line 165
def send_chain(method, *args)
  errors = []
  @storages.each do |s|
    begin
      return s.public_send(method, *args)
    rescue StandardError => e
      errors << e
      yield e
    end
  end

  raise AllFailedError.new("#{self.class}##{method} failed for all storage backends", errors)
end