class Semian::CircuitBreaker

:nodoc:

def acquire(resource = nil, &block)

def acquire(resource = nil, &block)
  return yield if disabled?
  transition_to_half_open if transition_to_half_open?
  raise OpenCircuitError unless request_allowed?
  result = nil
  begin
    result = maybe_with_half_open_resource_timeout(resource, &block)
  rescue *@exceptions => error
    if !error.respond_to?(:marks_semian_circuits?) || error.marks_semian_circuits?
      mark_failed(error)
    end
    raise error
  else
    mark_success
  end
  result
end

def destroy

def destroy
  @errors.destroy
  @successes.destroy
  @state.destroy
end

def disabled?

def disabled?
  ENV["SEMIAN_CIRCUIT_BREAKER_DISABLED"] || ENV["SEMIAN_DISABLED"]
end

def error_threshold_reached?

def error_threshold_reached?
  @errors.size == @error_count_threshold
end

def error_timeout_expired?

def error_timeout_expired?
  last_error_time = @errors.last
  return false unless last_error_time
  Time.at(last_error_time) + @error_timeout < Time.now
end

def in_use?

def in_use?
  !error_timeout_expired? && !@errors.empty?
end

def initialize(name, exceptions:, success_threshold:, error_threshold:,

def initialize(name, exceptions:, success_threshold:, error_threshold:,
  error_timeout:, implementation:, half_open_resource_timeout: nil,
  error_threshold_timeout: nil)
  @name = name.to_sym
  @success_count_threshold = success_threshold
  @error_count_threshold = error_threshold
  @error_threshold_timeout = error_threshold_timeout || error_timeout
  @error_timeout = error_timeout
  @exceptions = exceptions
  @half_open_resource_timeout = half_open_resource_timeout
  @errors = implementation::SlidingWindow.new(max_size: @error_count_threshold)
  @successes = implementation::Integer.new
  @state = implementation::State.new
  reset
end

def log_state_transition(new_state)

def log_state_transition(new_state)
  return if @state.nil? || new_state == @state.value
  str = "[#{self.class.name}] State transition from #{@state.value} to #{new_state}."
  str += " success_count=#{@successes.value} error_count=#{@errors.size}"
  str += " success_count_threshold=#{@success_count_threshold} error_count_threshold=#{@error_count_threshold}"
  str += " error_timeout=#{@error_timeout} error_last_at=\"#{@errors.last}\""
  str += " name=\"#{@name}\""
  if new_state == :open && @last_error
    str += " last_error_message=#{@last_error.message.inspect}"
  end
  Semian.logger.info(str)
end

def mark_failed(error)

def mark_failed(error)
  push_error(error)
  push_time(@errors)
  if closed?
    transition_to_open if error_threshold_reached?
  elsif half_open?
    transition_to_open
  end
end

def mark_success

def mark_success
  return unless half_open?
  @successes.increment
  transition_to_close if success_threshold_reached?
end

def maybe_with_half_open_resource_timeout(resource, &block)

def maybe_with_half_open_resource_timeout(resource, &block)
  result =
    if half_open? && @half_open_resource_timeout && resource.respond_to?(:with_resource_timeout)
      resource.with_resource_timeout(@half_open_resource_timeout) do
        block.call
      end
    else
      block.call
    end
  result
end

def notify_state_transition(new_state)

def notify_state_transition(new_state)
  Semian.notify(:state_change, self, nil, nil, state: new_state)
end

def push_error(error)

def push_error(error)
  @last_error = error
end

def push_time(window, time: Time.now)

def push_time(window, time: Time.now)
  window.reject! { |err_time| err_time + @error_threshold_timeout < time.to_i }
  window << time.to_i
end

def request_allowed?

def request_allowed?
  closed? || half_open? || transition_to_half_open?
end

def reset

def reset
  @errors.clear
  @successes.reset
  transition_to_close
end

def success_threshold_reached?

def success_threshold_reached?
  @successes.value >= @success_count_threshold
end

def transition_to_close

def transition_to_close
  notify_state_transition(:closed)
  log_state_transition(:closed)
  @state.close!
  @errors.clear
end

def transition_to_half_open

def transition_to_half_open
  notify_state_transition(:half_open)
  log_state_transition(:half_open)
  @state.half_open!
  @successes.reset
end

def transition_to_half_open?

def transition_to_half_open?
  open? && error_timeout_expired? && !half_open?
end

def transition_to_open

def transition_to_open
  notify_state_transition(:open)
  log_state_transition(:open)
  @state.open!
end