class Rester::Utils::CircuitBreaker

Attributes

block[R]
failure_count[R]
last_failed_at[R]
retry_period[R]
threshold[R]

Public Class Methods

new(opts={}, &block) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 14
def initialize(opts={}, &block)
  @_synchronizer = Mutex.new
  @_retry_lock = Mutex.new
  self.threshold = opts[:threshold]
  self.retry_period = opts[:retry_period]
  @block = block
  reset
end

Public Instance Methods

call(*args) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 52
def call(*args)
  if closed?
    _call(*args)
  elsif half_open? && @_retry_lock.try_lock
    # Ensure only one thread can retry.
    begin
      _call(*args)
    ensure
      @_retry_lock.unlock
    end
  else
    fail CircuitOpenError
  end
end
closed?() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 31
def closed?
  !reached_threshold?
end
half_open?() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 35
def half_open?
  !closed? && retry_period_passed?
end
on_close(&block) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 27
def on_close(&block)
  _callbacks[:close] = block
end
on_open(&block) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 23
def on_open(&block)
  _callbacks[:open] = block
end
open?() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 39
def open?
  !closed? && !half_open?
end
reached_threshold?() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 43
def reached_threshold?
  failure_count >= threshold
end
reset() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 67
def reset
  _synchronize do
    @failure_count = 0
    @last_failed_at = nil
  end
end
retry_period_passed?() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 47
def retry_period_passed?
  lf_at = last_failed_at
  !lf_at || (Time.now - lf_at) > retry_period
end

Protected Instance Methods

retry_period=(retry_period) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 82
def retry_period=(retry_period)
  unless (@retry_period = (retry_period || 1).to_f) > 0
    fail ArgumentError, 'retry_period must be > 0'
  end
end
threshold=(threshold) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 76
def threshold=(threshold)
  unless (@threshold = (threshold || 3).to_i) > 0
    fail ArgumentError, 'threshold must be > 0'
  end
end

Private Instance Methods

_call(*args) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 90
def _call(*args)
  begin
    block.call(*args).tap { _record_success }
  rescue
    _record_failure
    raise
  end
end
_call_on(type) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 103
def _call_on(type)
  (cb = _callbacks[type]) && cb.call
end
_callbacks() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 99
def _callbacks
  @__callbacks ||= {}
end
_record_failure() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 121
def _record_failure
  if @failure_count < threshold
    _synchronize do
      if @failure_count < threshold
        @failure_count += 1

        # If the threshold has now been reached, we're opening the circuit.
        _call_on(:open) if @failure_count == threshold
      end
    end
  end

  @last_failed_at = Time.now
end
_record_success() click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 111
def _record_success
  if @failure_count > 0
    _synchronize do
      # If the threshold had been reached, we're now closing the circuit.
      _call_on(:close) if @failure_count == threshold
      @failure_count = 0
    end
  end
end
_synchronize(&block) click to toggle source
# File lib/rester/utils/circuit_breaker.rb, line 107
def _synchronize(&block)
  @_synchronizer.synchronize(&block)
end