Class: Supervision::CircuitControl

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/supervision/circuit_control.rb

Overview

A class responsible for controling state of the circuit

Constant Summary collapse

MAX_THREAD_LIFETIME =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ CircuitControl

Create a circuit control

Parameters:

  • options (Hash) (defaults to: {})


33
34
35
36
37
38
39
40
# File 'lib/supervision/circuit_control.rb', line 33

def initialize(options = {})
  @config            = Configuration.new(options)
  @failure_count     = Atomic.new(0)
  @last_failure_time = Atomic.new
  @lock              = Mutex.new
  @monitor           = CircuitMonitor.new
  fsm
end

Instance Attribute Details

#configObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The circuit configuration



14
15
16
# File 'lib/supervision/circuit_control.rb', line 14

def config
  @config
end

#monitorObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The circuit performance monitor



24
25
26
# File 'lib/supervision/circuit_control.rb', line 24

def monitor
  @monitor
end

#schedulerObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The reset timeout scheduler



19
20
21
# File 'lib/supervision/circuit_control.rb', line 19

def scheduler
  @scheduler
end

Instance Method Details

#fail_fast!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Fail fast on any call



116
117
118
119
# File 'lib/supervision/circuit_control.rb', line 116

def fail_fast!
  monitor.measure(:open_circuit)
  raise CircuitBreakerOpenError
end

#failure_countInteger

Total failure count for current circuit

Returns:

  • (Integer)


87
88
89
# File 'lib/supervision/circuit_control.rb', line 87

def failure_count
  @failure_count.value
end

#failure_count_exceeded?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


124
125
126
# File 'lib/supervision/circuit_control.rb', line 124

def failure_count_exceeded?
  failure_count > @config.max_failures
end

#fsmFiniteMachine

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates internal finite state machine to transitions through three states :closed, :open and :half_open.

Returns:

  • (FiniteMachine)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/supervision/circuit_control.rb', line 49

def fsm
  context = self
  @fsm ||= FiniteMachine.define do
    initial :closed

    target context

    events do
      event :trip,         [:closed, :half_open] => :open
      event :attempt_reset, :open                => :half_open
      event :reset,         :half_open           => :closed
    end

    callbacks do
      on_enter :closed do |event|
        reset_failure
      end

      on_enter :open do |event|
        measure_timeout
        fail_fast!
      end

      on_enter :half_open do |event|
        monitor.measure(:half_open_circuit)
      end
    end
  end
end

#handle_failure(error = nil) ⇒ Object

Handler exception



151
152
153
154
155
156
# File 'lib/supervision/circuit_control.rb', line 151

def handle_failure(error = nil)
  fail_fast! if fsm.open?
  record_failure
  monitor.record_failure
  trip if failure_count_exceeded? || fsm.half_open?
end

#last_failure_timeTime

Last time failure occured

Returns:

  • (Time)


96
97
98
# File 'lib/supervision/circuit_control.rb', line 96

def last_failure_time
  @last_failure_time.value
end

#max_thread_lifetimeTime

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Estimate maximum duration the scheduling thread should live

Returns:

  • (Time)


220
221
222
# File 'lib/supervision/circuit_control.rb', line 220

def max_thread_lifetime
  @config.reset_timeout + 100.milli
end

#measure_timeoutObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Measure remaining timeout



188
189
190
191
192
193
194
195
196
197
# File 'lib/supervision/circuit_control.rb', line 188

def measure_timeout
  @scheduler = Thread.new do
    Thread.current.abort_on_exception = true
    thread = Thread.current
    thread[:created_at] = Time.now
    @lock.synchronize do
      run_loop(thread)
    end
  end
end

#record_failureObject

Record failure count



170
171
172
173
174
175
# File 'lib/supervision/circuit_control.rb', line 170

def record_failure
  if fsm.closed? || fsm.half_open?
    @failure_count.update { |v| v + 1 }
    @last_failure_time.value = Time.now
  end
end

#record_successObject

Record successful call



161
162
163
164
165
# File 'lib/supervision/circuit_control.rb', line 161

def record_success
  reset if fsm.half_open?
  reset_failure
  monitor.record_success
end

#reset!nil

Force closed state and reset failure statistics

Returns:

  • (nil)


105
106
107
108
109
# File 'lib/supervision/circuit_control.rb', line 105

def reset!
  fsm.reset!
  reset_failure
  throw(:terminate) if @scheduler && @scheduler.alive?
end

#reset_failureObject

Resets failure count



180
181
182
183
# File 'lib/supervision/circuit_control.rb', line 180

def reset_failure
  @failure_count.value = 0
  @last_failure_time.value = nil
end

#run_loop(thread) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Run scheduler loop



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/supervision/circuit_control.rb', line 202

def run_loop(thread)
  catch(:terminate) do
    loop do
      if tripped?
        attempt_reset && break
      elsif Time.now - thread[:created_at] > max_thread_lifetime
        thread.kill if thread.alive?
        break
      end
    end
  end
end

#timeout_exceeded?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if remaining duration until reset has been exceeded

Returns:

  • (Boolean)

    whether or not the breaker will attempt a reset by transitioning to :half_open state



142
143
144
145
146
# File 'lib/supervision/circuit_control.rb', line 142

def timeout_exceeded?
  return false unless last_failure_time
  timeout = Time.now - last_failure_time
  timeout > @config.reset_timeout
end

#tripped?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


131
132
133
# File 'lib/supervision/circuit_control.rb', line 131

def tripped?
  fsm.open? && timeout_exceeded?
end