lib/supervision/circuit_breaker.rb
# encoding: utf-8
module Supervision
# A class responsible for protecting remote calls
class CircuitBreaker
include Timeout
extend Forwardable
attr_reader :control
attr_reader :name
def_delegators :@control, :current, :max_failures, :call_timeout,
:reset_timeout
# Create a CircuitBreaker
#
# @example
# circuit = CircuitBreaker { ... }
#
# @api public
def initialize(options = {}, &block)
if block.nil?
raise InvalidParameterError, 'CircuitBreaker.new requires a block'
end
@name = options.delete(:name)
@control = CircuitControl.new(options)
@circuit = Atomic.new(block)
@mutex = Mutex.new
end
# Configure circuit instance parameters
#
# @yield [Configuration]
#
# @api public
def configure(&block)
control.config.configure(&block)
end
# Executes the dangerous call
#
# # TODO: this should distribute calls so we don't wait
# in sync call for timeout
#
# @api public
def call(*args)
handle_before
begin
result = dispatch(*args)
handle_success
result
rescue Exception => error
handle_failure(error)
end
end
# Reset this circuit to closed state
#
# @example
# supervision.reset!
#
# @return [nil]
#
# @api public
def reset!
control.reset!
end
# Define before handler
#
# @api public
def before(&block)
@before = block
self
end
# Define success handler
#
# @return [Supervision::CircuitBreaker]
#
# @api public
def on_success(&block)
@on_success = block
self
end
alias_method :on_closed, :on_success
# Define failure handler
#
# @return [Supervision::CircuitBreaker]
#
# @api public
def on_failure(&block)
@on_failure = block
self
end
alias_method :on_open, :on_failure
# Detailed string representation of this circuit
#
# @return [String]
#
# @api public
def inspect
"#<#{self.class.name}:#{object_id} @name=#{name}>"
end
# Detailed string representation of this circuit
#
# @return [String]
#
# @api public
def to_s
"#<#{self.class.name}:#{object_id} @name=#{name}>"
end
private
# Invoke before handler
#
# @api private
def handle_before
@before.call if @before
end
# Invoke success handler
#
# @api private
def handle_success
@on_success.call if @on_success
end
# Invoke failure handler and instrument circuit controller
#
# @api private
def handle_failure(error)
control.handle_failure(error)
@on_failure.call(error) if @on_failure
end
# Dispatch message to the current circuit
#
# @api private
def dispatch(*args)
result = timeout(control.call_timeout) do
@circuit.value.call(*args)
end
control.record_success
result
end
end # CircuitBreaker
end # Supervision