class RedisClient::CircuitBreaker

Constants

OpenCircuitError

Attributes

error_threshold[R]
error_threshold_timeout[R]
error_timeout[R]
success_threshold[R]

Public Class Methods

new(error_threshold:, error_timeout:, error_threshold_timeout: error_timeout, success_threshold: 0) click to toggle source
# File lib/redis_client/circuit_breaker.rb, line 23
def initialize(error_threshold:, error_timeout:, error_threshold_timeout: error_timeout, success_threshold: 0)
  @error_threshold = Integer(error_threshold)
  @error_threshold_timeout = Float(error_threshold_timeout)
  @error_timeout = Float(error_timeout)
  @success_threshold = Integer(success_threshold)
  @errors = []
  @successes = 0
  @state = :closed
  @lock = Mutex.new
end

Public Instance Methods

protect() { || ... } click to toggle source
# File lib/redis_client/circuit_breaker.rb, line 34
def protect
  if @state == :open
    refresh_state
  end

  case @state
  when :open
    raise OpenCircuitError, "Too many connection errors happened recently"
  when :closed
    begin
      yield
    rescue ConnectionError
      record_error
      raise
    end
  when :half_open
    begin
      result = yield
      record_success
      result
    rescue ConnectionError
      record_error
      raise
    end
  else
    raise "[BUG] RedisClient::CircuitBreaker unexpected @state (#{@state.inspect}})"
  end
end

Private Instance Methods

record_error() click to toggle source
# File lib/redis_client/circuit_breaker.rb, line 80
def record_error
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  expiry = now - @error_timeout
  @lock.synchronize do
    if @state == :closed
      @errors.reject! { |t| t < expiry }
    end
    @errors << now
    @successes = 0
    if @state == :half_open || (@state == :closed && @errors.size >= @error_threshold)
      @state = :open
    end
  end
end
record_success() click to toggle source
# File lib/redis_client/circuit_breaker.rb, line 95
def record_success
  return unless @state == :half_open

  @lock.synchronize do
    return unless @state == :half_open

    @successes += 1
    if @successes >= @success_threshold
      @state = :closed
    end
  end
end
refresh_state() click to toggle source
# File lib/redis_client/circuit_breaker.rb, line 65
def refresh_state
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @lock.synchronize do
    if @errors.last < (now - @error_timeout)
      if @success_threshold > 0
        @state = :half_open
        @successes = 0
      else
        @errors.clear
        @state = :closed
      end
    end
  end
end