Class: CMDx::CircuitBreaker

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

Overview

Declared via Task.circuit_breaker. After inputs resolve, consults shared store counters before the retry_on loop; opens after repeated failures and short-circuits until cool_off elapses.

Store keys come from StoreKey#task_key with class_scoped: true, plus :failures and :open suffix segments (see StoreKey#compose).

Constant Summary collapse

OPEN =
"open"
FAILURES =
"failures"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key, options = EMPTY_HASH) ⇒ CircuitBreaker

Returns a new instance of CircuitBreaker.

Parameters:

  • key (Symbol, String, Proc, #call, nil) —

    :class (default when enabling via options only), context Symbol, static String, or callable; nil disables the breaker

  • options (Hash{Symbol => Object}) (defaults to: EMPTY_HASH)

Options Hash (options):

  • :threshold (Integer) — default: 5 —

    failures before the circuit opens

  • :cool_off (Integer) — default: 30 —

    seconds the circuit stays open

  • :window (Integer) — default: 60 —

    sliding window for the failure counter

  • :namespace (String) — default: "cmdx:cb" —

    first segment of store keys

  • :on_open (Symbol) — default: :fail —

    :fail or :skip when open

  • :on (Array<Class>) — default: `[StandardError]` —

    exception classes that increment the failure counter

  • :if (Symbol, Proc, #call) —

    gate (task) before checking

  • :unless (Symbol, Proc, #call) —

    gate (task) before checking



39
40
41
42
# File 'lib/cmdx/circuit_breaker.rb', line 39

def initialize(key, options = EMPTY_HASH)
  @key     = key
  @options = options.freeze
end

Instance Attribute Details

#key ⇒ Object (readonly)

Returns the value of attribute key.



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

def key
  @key
end

Class Method Details

.disabled ⇒ CircuitBreaker

Returns disabled configuration.

Returns:



18
19
20
# File 'lib/cmdx/circuit_breaker.rb', line 18

def disabled
  DISABLED
end

Instance Method Details

#build(new_key, new_options) ⇒ CircuitBreaker

Returns a new CircuitBreaker layering new_options onto the current one. When new_key is non-nil it replaces the resolver; when new_key is nil and only options change, the existing resolver is kept. When the breaker was disabled and only options are passed, the resolver becomes :class.

Parameters:

  • new_key (Symbol, String, Proc, #call, nil)
  • new_options (Hash{Symbol => Object})

Returns:



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/cmdx/circuit_breaker.rb', line 98

def build(new_key, new_options)
  return self if new_key.nil? && new_options.empty?

  merged_key =
    if new_key.nil?
      enabled? || new_options.empty? ? @key : StoreKey::CLASS_KEY
    else
      new_key
    end
  merged_options = @options.merge(new_options)

  self.class.new(merged_key, merged_options)
end

#condition_if ⇒ Symbol, ...

Returns:

  • (Symbol, Proc, #call, nil)


75
76
77
# File 'lib/cmdx/circuit_breaker.rb', line 75

def condition_if
  @options[:if]
end

#condition_unless ⇒ Symbol, ...

Returns:

  • (Symbol, Proc, #call, nil)


80
81
82
# File 'lib/cmdx/circuit_breaker.rb', line 80

def condition_unless
  @options[:unless]
end

#cool_off ⇒ Integer

Returns:

  • (Integer)


55
56
57
# File 'lib/cmdx/circuit_breaker.rb', line 55

def cool_off
  @options[:cool_off] || 30
end

#enabled? ⇒ Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/cmdx/circuit_breaker.rb', line 85

def enabled?
  !key.nil?
end

#namespace ⇒ String

Returns:

  • (String)


45
46
47
# File 'lib/cmdx/circuit_breaker.rb', line 45

def namespace
  @options[:namespace] || "cmdx:cb"
end

#on_exceptions ⇒ Array<Class>

Returns:

  • (Array<Class>)


65
66
67
# File 'lib/cmdx/circuit_breaker.rb', line 65

def on_exceptions
  @options.fetch(:on) { [StandardError] }.flatten
end

#on_open ⇒ Symbol

Returns :fail or :skip.

Returns:

  • (Symbol) —

    :fail or :skip



70
71
72
# File 'lib/cmdx/circuit_breaker.rb', line 70

def on_open
  @options[:on_open] || :fail
end

#process(task) { ... } ⇒ Object

Note:

When the circuit is open, signals fail! or skip! on task — control does not return to the caller of #process.

Runs the given block when the circuit is closed (or inactive). Records failures for configured exception classes and clears the failure counter after a successful run.

Parameters:

Yields:

  • invoked when the circuit is closed or the breaker is inactive

Yield Returns:

  • (Object) —

    return value from the wrapped work

Returns:

  • (Object) —

    the block's return value

Raises:

  • (ArgumentError) —

    when on_open is not :fail or :skip



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cmdx/circuit_breaker.rb', line 123

def process(task)
  return yield unless enabled?
  return yield unless Util.satisfied?(condition_if, condition_unless, task)

  store_key = StoreKey.task_key(task, namespace:, resolver: key, class_scoped: true)
  return yield if store_key.nil?

  open_key     = StoreKey.compose(store_key, OPEN)
  failures_key = StoreKey.compose(store_key, FAILURES)
  open!(task, open_key) if circuit_open?(open_key)

  begin
    yield
  rescue StandardError => e
    record_failure(e, failures_key, open_key, task)
    raise e
  else
    clear_failures(failures_key, task)
  end
end

#threshold ⇒ Integer

Returns:

  • (Integer)


50
51
52
# File 'lib/cmdx/circuit_breaker.rb', line 50

def threshold
  @options[:threshold] || 5
end

#window ⇒ Integer

Returns:

  • (Integer)


60
61
62
# File 'lib/cmdx/circuit_breaker.rb', line 60

def window
  @options[:window] || 60
end