Class: CircuitBreaker::CircuitHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/circuit_breaker/circuit_handler.rb

Overview

CircuitHandler is stateless, so the circuit_state gets mixed in with the calling object.

Constant Summary collapse

DEFAULT_FAILURE_THRESHOLD =
5
DEFAULT_FAILURE_TIMEOUT =
5
DEFAULT_INVOCATION_TIMEOUT =
30
DEFAULT_EXCLUDED_EXCEPTIONS =
[]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logger = nil) ⇒ CircuitHandler

Returns a new instance of CircuitHandler.



41
42
43
44
45
46
47
# File 'lib/circuit_breaker/circuit_handler.rb', line 41

def initialize(logger = nil)
  @logger = logger
  @failure_threshold = DEFAULT_FAILURE_THRESHOLD
  @failure_timeout = DEFAULT_FAILURE_TIMEOUT
  @invocation_timeout = DEFAULT_INVOCATION_TIMEOUT
  @excluded_exceptions = DEFAULT_EXCLUDED_EXCEPTIONS
end

Instance Attribute Details

#excluded_exceptionsObject

The exceptions which should be ignored if happens, they are not counted as failures



29
30
31
# File 'lib/circuit_breaker/circuit_handler.rb', line 29

def excluded_exceptions
  @excluded_exceptions
end

#failure_thresholdObject

The number of failures needed to trip the breaker.



14
15
16
# File 'lib/circuit_breaker/circuit_handler.rb', line 14

def failure_threshold
  @failure_threshold
end

#failure_timeoutObject

The period of time in seconds before attempting to reset the breaker.



19
20
21
# File 'lib/circuit_breaker/circuit_handler.rb', line 19

def failure_timeout
  @failure_timeout
end

#invocation_timeoutObject

The period of time the circuit_method has to return before a timeout exception is thrown.



24
25
26
# File 'lib/circuit_breaker/circuit_handler.rb', line 24

def invocation_timeout
  @invocation_timeout
end

#loggerObject

Optional logger.



34
35
36
# File 'lib/circuit_breaker/circuit_handler.rb', line 34

def logger
  @logger
end

Instance Method Details

#handle(circuit_state, method, *args) ⇒ Object

Handles the method covered by the circuit breaker.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/circuit_breaker/circuit_handler.rb', line 59

def handle(circuit_state, method, *args)
  if is_tripped(circuit_state)
    @logger.debug("handle: breaker is tripped, refusing to execute: #{circuit_state.inspect}") if @logger
    on_circuit_open(circuit_state)
  end

  begin
    out = nil
    Timeout.timeout(@invocation_timeout, CircuitBreaker::CircuitBrokenException) do
      out = method[*args]
      on_success(circuit_state)
    end
  rescue Exception => e
    on_failure(circuit_state) unless @excluded_exceptions.include?(e.class)
    raise
  end
  return out
end

#is_failure_threshold_reached(circuit_state) ⇒ Object

Returns true when the number of failures is sufficient to trip the breaker, false otherwise.



81
82
83
84
85
86
# File 'lib/circuit_breaker/circuit_handler.rb', line 81

def is_failure_threshold_reached(circuit_state)
  out = (circuit_state.failure_count > failure_threshold)
  @logger.debug("is_failure_threshold_reached: #{circuit_state.failure_count} > #{failure_threshold} == #{out}") if @logger

  return out
end

#is_timeout_exceeded(circuit_state) ⇒ Object

Returns true if enough time has elapsed since the last failure time, false otherwise.



91
92
93
94
95
96
97
# File 'lib/circuit_breaker/circuit_handler.rb', line 91

def is_timeout_exceeded(circuit_state)
  now = Time.now

  time_since = now - circuit_state.last_failure_time
  @logger.debug("timeout_exceeded: time since last failure = #{time_since.inspect}") if @logger
  return time_since >= failure_timeout
end

#is_tripped(circuit_state) ⇒ Object

Returns true if the circuit breaker is still open and the timeout has not been exceeded, false otherwise.



103
104
105
106
107
108
109
110
111
# File 'lib/circuit_breaker/circuit_handler.rb', line 103

def is_tripped(circuit_state)

  if circuit_state.open? && is_timeout_exceeded(circuit_state)
    @logger.debug("is_tripped: attempting reset into half open state for #{circuit_state.inspect}") if @logger
    circuit_state.attempt_reset
  end

  return circuit_state.open?
end

#new_circuit_stateObject

Returns a new CircuitState instance.



52
53
54
# File 'lib/circuit_breaker/circuit_handler.rb', line 52

def new_circuit_state
  ::CircuitBreaker::CircuitState.new
end

#on_circuit_open(circuit_state) ⇒ Object

Called when a call is made and the circuit is open. Raises a CircuitBrokenException exception.



148
149
150
151
152
# File 'lib/circuit_breaker/circuit_handler.rb', line 148

def on_circuit_open(circuit_state)
  @logger.debug("on_circuit_open: raising for #{circuit_state.inspect}") if @logger

  raise CircuitBreaker::CircuitBrokenException.new("Circuit broken, please wait for timeout", circuit_state)
end

#on_failure(circuit_state) ⇒ Object

Called when an individual failure happens.



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/circuit_breaker/circuit_handler.rb', line 133

def on_failure(circuit_state)
  @logger.debug("on_failure: circuit_state = #{circuit_state.inspect}") if @logger

  circuit_state.increment_failure_count

  if is_failure_threshold_reached(circuit_state) || circuit_state.half_open?
    # Set us into a closed state.
    @logger.debug("on_failure: tripping circuit breaker #{circuit_state.inspect}") if @logger
    circuit_state.trip
  end
end

#on_success(circuit_state) ⇒ Object

Called when an individual success happens.



116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/circuit_breaker/circuit_handler.rb', line 116

def on_success(circuit_state)
  @logger.debug("on_success: #{circuit_state.inspect}") if @logger

  if circuit_state.closed?
    @logger.debug("on_success: reset_failure_count #{circuit_state.inspect}") if @logger
    circuit_state.reset_failure_count
  end

  if circuit_state.half_open?
    @logger.debug("on_success: reset circuit #{circuit_state.inspect}") if @logger
    circuit_state.reset
  end
end