Class: SafeMemoize::Stores::CircuitBreaker
- Defined in:
- lib/safe_memoize/stores/circuit_breaker.rb
Overview
Wraps any Base store adapter with a circuit breaker that silently falls back to the per-instance in-process cache when the external store is unavailable, rather than propagating exceptions to callers.
=== States
- +:closed+ — normal; every call goes through to the wrapped store; consecutive errors are counted
- +:open+ — tripped; reads return Base::MISS and writes are no-ops so the memoize wrapper falls back to the per-instance hash; no calls reach the wrapped store until the probe interval elapses
- +:half_open+ — probe period (probe interval elapsed); calls are let through to the wrapped store; the first success closes the circuit, any failure re-opens it and resets the timer
Any successful call while the circuit is +:closed+ resets the consecutive error counter, so transient blips do not accumulate toward the threshold.
Constant Summary collapse
- DEFAULT_ERROR_THRESHOLD =
5- DEFAULT_PROBE_INTERVAL =
30.0
Constants inherited from Base
Instance Attribute Summary collapse
-
#error_threshold ⇒ Integer
readonly
Number of consecutive errors that trip the circuit.
-
#probe_interval ⇒ Float
readonly
Seconds after tripping before a probe is attempted.
-
#wrapped_store ⇒ Stores::Base
readonly
The wrapped inner store.
Instance Method Summary collapse
-
#clear ⇒ Object
Clear the wrapped store.
-
#delete(key) ⇒ Object
Delete from the wrapped store.
-
#error_count ⇒ Integer
Returns the current consecutive error count.
-
#initialize(store, error_threshold: DEFAULT_ERROR_THRESHOLD, probe_interval: DEFAULT_PROBE_INTERVAL) ⇒ CircuitBreaker
constructor
A new instance of CircuitBreaker.
-
#keys ⇒ Object
Returns live keys from the wrapped store, or an empty array when the circuit is open or the store raises.
-
#open? ⇒ Boolean
Returns +true+ when the circuit is not fully closed (i.e. open or half-open).
-
#read(key) ⇒ Object
Read from the wrapped store, returning Base::MISS on error or when the circuit is open instead of raising.
-
#reset! ⇒ void
Manually resets the circuit to +:closed+, clearing the error counter.
-
#state ⇒ Symbol
Returns the current circuit state: +:closed+, +:open+, or +:half_open+.
-
#write(key, value, expires_in: nil) ⇒ Object
Write to the wrapped store, silently swallowing errors so the caller's return value is unaffected.
Methods inherited from Base
Constructor Details
#initialize(store, error_threshold: DEFAULT_ERROR_THRESHOLD, probe_interval: DEFAULT_PROBE_INTERVAL) ⇒ CircuitBreaker
Returns a new instance of CircuitBreaker.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 51 def initialize(store, error_threshold: DEFAULT_ERROR_THRESHOLD, probe_interval: DEFAULT_PROBE_INTERVAL) unless store.is_a?(Base) raise ArgumentError, "CircuitBreaker requires a Stores::Base instance (got #{store.class})" end @wrapped_store = store @error_threshold = Integer(error_threshold) @probe_interval = Float(probe_interval) raise ArgumentError, "error_threshold must be positive" unless @error_threshold > 0 raise ArgumentError, "probe_interval must be positive" unless @probe_interval > 0 @mutex = Mutex.new @error_count = 0 @opened_at = nil end |
Instance Attribute Details
#error_threshold ⇒ Integer (readonly)
Returns number of consecutive errors that trip the circuit.
42 43 44 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 42 def error_threshold @error_threshold end |
#probe_interval ⇒ Float (readonly)
Returns seconds after tripping before a probe is attempted.
44 45 46 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 44 def probe_interval @probe_interval end |
#wrapped_store ⇒ Stores::Base (readonly)
Returns the wrapped inner store.
40 41 42 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 40 def wrapped_store @wrapped_store end |
Instance Method Details
#clear ⇒ Object
Clear the wrapped store. Errors are recorded but not re-raised.
104 105 106 107 108 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 104 def clear @wrapped_store.clear rescue record_failure end |
#delete(key) ⇒ Object
Delete from the wrapped store. A no-op when the circuit is open.
95 96 97 98 99 100 101 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 95 def delete(key) return if current_state == :open @wrapped_store.delete(key) rescue record_failure end |
#error_count ⇒ Integer
Returns the current consecutive error count.
135 136 137 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 135 def error_count @mutex.synchronize { @error_count } end |
#keys ⇒ Object
Returns live keys from the wrapped store, or an empty array when the circuit is open or the store raises.
112 113 114 115 116 117 118 119 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 112 def keys return [] if current_state == :open @wrapped_store.keys rescue record_failure [] end |
#open? ⇒ Boolean
Returns +true+ when the circuit is not fully closed (i.e. open or half-open).
129 130 131 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 129 def open? current_state != :closed end |
#read(key) ⇒ Object
Read from the wrapped store, returning Base::MISS on error or when the circuit is open instead of raising.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 70 def read(key) st = current_state return MISS if st == :open result = @wrapped_store.read(key) record_success(st) result rescue record_failure MISS end |
#reset! ⇒ void
This method returns an undefined value.
Manually resets the circuit to +:closed+, clearing the error counter.
141 142 143 144 145 146 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 141 def reset! @mutex.synchronize do @error_count = 0 @opened_at = nil end end |
#state ⇒ Symbol
Returns the current circuit state: +:closed+, +:open+, or +:half_open+.
123 124 125 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 123 def state current_state end |
#write(key, value, expires_in: nil) ⇒ Object
Write to the wrapped store, silently swallowing errors so the caller's return value is unaffected. A no-op when the circuit is open.
84 85 86 87 88 89 90 91 92 |
# File 'lib/safe_memoize/stores/circuit_breaker.rb', line 84 def write(key, value, expires_in: nil) st = current_state return if st == :open @wrapped_store.write(key, value, expires_in: expires_in) record_success(st) rescue record_failure end |