Class: Faulty::Circuit
- Inherits:
-
Object
- Object
- Faulty::Circuit
- Defined in:
- lib/faulty/circuit.rb
Overview
Runs code protected by a circuit breaker
https://www.martinfowler.com/bliki/CircuitBreaker.html
A circuit is intended to protect against repeated calls to a failing external dependency. For example, a vendor API may be failing continuously. In that case, we trip the circuit breaker and stop calling that API for a specified cool-down period.
Once the cool-down passes, we try the API again, and if it succeeds, we reset the circuit.
Why isn't there a timeout option?
Timeout is inherently unsafe, and should not be used blindly. See Why Ruby's timeout is Dangerous.
You should prefer a network timeout like open_timeout
and read_timeout
, or
write your own code to periodically check how long it has been running.
If you're sure you want ruby's generic Timeout, you can apply it yourself
inside the circuit run block.
Defined Under Namespace
Classes: Options
Constant Summary collapse
- CACHE_REFRESH_SUFFIX =
'.faulty_refresh'
Instance Attribute Summary collapse
-
#cache ⇒ Cache::Interface
readonly
Cache::Null.new
. -
#cache_expires_in ⇒ Integer?
readonly
The number of seconds to keep cached results.
-
#cache_refresh_jitter ⇒ Integer
readonly
The maximum number of seconds to randomly add or subtract from
cache_refreshes_after
when determining whether to refresh the cache. -
#cache_refreshes_after ⇒ Integer?
readonly
The number of seconds after which we attempt to refresh the cache even if it's not expired.
-
#cool_down ⇒ Integer
readonly
The number of seconds the circuit will stay open after it is tripped.
-
#error_mapper ⇒ Module, #call
readonly
Used by patches to set the namespace module for the faulty errors that will be raised.
-
#errors ⇒ Error+
readonly
An array of errors that are considered circuit failures.
-
#evaluation_window ⇒ Integer
readonly
The number of seconds of history that will be evaluated to determine the failure rate for a circuit.
-
#exclude ⇒ Error+
readonly
An array of errors that will not be captured by Faulty.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#notifier ⇒ Events::Notifier
readonly
A Faulty notifier.
-
#rate_threshold ⇒ Float
readonly
The minimum failure rate required to trip the circuit.
-
#registry ⇒ CircuitRegistry
readonly
memoization of circuits.
-
#sample_threshold ⇒ Integer
readonly
The minimum number of runs required before a circuit can trip.
Instance Method Summary collapse
-
#history ⇒ Array<Array>
Get the history of runs of this circuit.
-
#initialize(name, **options) {|Options| ... } ⇒ Circuit
constructor
A new instance of Circuit.
-
#inspect ⇒ String
Text representation of the circuit.
-
#lock_closed! ⇒ self
Force the circuit to stay closed until unlocked.
-
#lock_open! ⇒ self
Force the circuit to stay open until unlocked.
-
#options ⇒ Options
Get the options for this circuit.
-
#reset! ⇒ self
Reset this circuit to its initial state.
-
#run(cache: nil) { ... } ⇒ Object
Run a block protected by this circuit.
-
#status ⇒ Status
Get the current status of the circuit.
- #try_run(**options) { ... } ⇒ Result<Object, Error>
-
#unlock! ⇒ self
Remove any open or closed locks.
Constructor Details
#initialize(name, **options) {|Options| ... } ⇒ Circuit
Returns a new instance of Circuit.
193 194 195 196 197 198 199 200 |
# File 'lib/faulty/circuit.rb', line 193 def initialize(name, **, &block) raise ArgumentError, 'name must be a String' unless name.is_a?(String) @name = name @given_options = Options.new(, &block) @pulled_options = nil @options_pushed = false end |
Instance Attribute Details
#cache ⇒ Cache::Interface (readonly)
Cache::Null.new
. Unlike Faulty#initialize, this is not wrapped in
Faulty::Cache::AutoWire by default.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#cache_expires_in ⇒ Integer? (readonly)
Returns The number of seconds to keep
cached results. A value of nil will keep the cache indefinitely.
Default 86400
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#cache_refresh_jitter ⇒ Integer (readonly)
Returns The maximum number of seconds to randomly add or
subtract from cache_refreshes_after
when determining whether to
refresh the cache. A non-zero value helps reduce a "thundering herd"
cache refresh in most scenarios. Set to 0
to disable jitter.
Default 0.2 * cache_refreshes_after
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#cache_refreshes_after ⇒ Integer? (readonly)
Returns The number of seconds after which we attempt
to refresh the cache even if it's not expired. If the circuit fails,
we continue serving the value from cache until cache_expires_in
.
A value of nil
disables cache refreshing.
Default 900
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#cool_down ⇒ Integer (readonly)
Returns The number of seconds the circuit will stay open after it is tripped. Default 300.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#error_mapper ⇒ Module, #call (readonly)
Returns Used by patches to set the namespace module for
the faulty errors that will be raised. Should be a module or a callable.
If given a module, the circuit assumes the module has error classes
in that module. If given an object that responds to #call
(a proc
or lambda), the return value of the callable will be used. The callable
is called with (error_name
, cause_error
, circuit
). Default Faulty
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#errors ⇒ Error+ (readonly)
Returns An array of errors that are considered circuit
failures. Default [StandardError]
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#evaluation_window ⇒ Integer (readonly)
Returns The number of seconds of history that
will be evaluated to determine the failure rate for a circuit.
Default 60
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#exclude ⇒ Error+ (readonly)
Returns An array of errors that will not be
captured by Faulty. These errors will not be considered circuit
failures. Default []
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
29 30 31 |
# File 'lib/faulty/circuit.rb', line 29 def name @name end |
#notifier ⇒ Events::Notifier (readonly)
Returns A Faulty notifier. Default Events::Notifier.new
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#rate_threshold ⇒ Float (readonly)
Returns The minimum failure rate required to trip
the circuit. For example, 0.5
requires at least a 50% failure rate to
trip. Default 0.5
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#registry ⇒ CircuitRegistry (readonly)
memoization of circuits.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
#sample_threshold ⇒ Integer (readonly)
Returns The minimum number of runs required before
a circuit can trip. A value of 1 means that the circuit will trip
immediately when a failure occurs. Default 3
.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/faulty/circuit.rb', line 91 Options = Struct.new( :cache_expires_in, :cache_refreshes_after, :cache_refresh_jitter, :cool_down, :evaluation_window, :rate_threshold, :sample_threshold, :errors, :error_mapper, :error_module, :exclude, :cache, :notifier, :storage, :registry ) do include ImmutableOptions # Get the options stored in the storage backend # # @return [Hash] A hash of stored options def for_storage { cool_down: cool_down, evaluation_window: evaluation_window, rate_threshold: rate_threshold, sample_threshold: sample_threshold } end def defaults { cache_expires_in: 86_400, cache_refreshes_after: 900, cool_down: 300, errors: [StandardError], error_mapper: Faulty, exclude: [], evaluation_window: 60, rate_threshold: 0.5, sample_threshold: 3 } end def required %i[ cache cool_down errors error_mapper exclude evaluation_window rate_threshold sample_threshold notifier storage ] end def finalize self.cache ||= Cache::Default.new self.notifier ||= Events::Notifier.new self.storage ||= Storage::Memory.new self.errors = [errors] if errors && !errors.is_a?(Array) self.exclude = [exclude] if exclude && !exclude.is_a?(Array) unless cache_refreshes_after.nil? self.cache_refresh_jitter = 0.2 * cache_refreshes_after end deprecated_error_module end private def deprecated_error_module return unless error_module Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0') self.error_mapper = error_module end end |
Instance Method Details
#history ⇒ Array<Array>
Get the history of runs of this circuit
The history is an array of tuples where the first value is the run time, and the second value is a boolean which is true if the run was successful.
382 383 384 |
# File 'lib/faulty/circuit.rb', line 382 def history storage.history(self) end |
#inspect ⇒ String
Returns Text representation of the circuit.
176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/faulty/circuit.rb', line 176 def inspect interested_opts = %i[ cache_expires_in cache_refreshes_after cache_refresh_jitter cool_down evaluation_window rate_threshold sample_threshold errors exclude ] = .each_pair.map { |k, v| "#{k}: #{v}" if interested_opts.include?(k) }.compact.join(', ') %(#<#{self.class.name} name: #{name}, state: #{status.state}, options: { #{} }>) end |
#lock_closed! ⇒ self
Force the circuit to stay closed until unlocked
339 340 341 342 |
# File 'lib/faulty/circuit.rb', line 339 def lock_closed! storage.lock(self, :closed) self end |
#lock_open! ⇒ self
Force the circuit to stay open until unlocked
331 332 333 334 |
# File 'lib/faulty/circuit.rb', line 331 def lock_open! storage.lock(self, :open) self end |
#options ⇒ Options
Get the options for this circuit
If this circuit has been run, these will the options exactly as given to new. However, if this circuit has not yet been run, these options will be supplemented by the last-known options from the circuit storage.
Once a circuit is run, the given options are pushed to circuit storage to be persisted.
This is to allow circuit objects to behave as expected in contexts where the exact options for a circuit are not known such as an admin dashboard or in a debug console.
Note that this distinction isn't usually important unless using distributed circuit storage like the Redis storage backend.
244 245 246 247 248 249 250 |
# File 'lib/faulty/circuit.rb', line 244 def return @given_options if @options_pushed return @pulled_options if @pulled_options stored = @given_options.storage.(self) @pulled_options = stored ? @given_options.dup_with(stored) : @given_options end |
#reset! ⇒ self
Reset this circuit to its initial state
This removes the current state, all history, and locks
357 358 359 360 361 362 |
# File 'lib/faulty/circuit.rb', line 357 def reset! @options_pushed = false @pulled_options = nil storage.reset(self) self end |
#run(cache: nil) { ... } ⇒ Object
Run a block protected by this circuit
If the circuit is closed, the block will run. Any exceptions raised inside the block will be checked against the error and exclude options to determine whether that error should be captured. If the error is captured, this run will be recorded as a failure.
If the circuit exceeds the failure conditions, this circuit will be tripped and marked as open. Any future calls to run will not execute the block, but instead wait for the cool down period. Once the cool down period passes, the circuit transitions to half-open, and the block will be allowed to run.
If the circuit fails again while half-open, the circuit will be closed for a second cool down period. However, if the circuit completes successfully, the circuit will be closed and reset to its initial state.
When this is run, the given options are persisted to the storage backend.
316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/faulty/circuit.rb', line 316 def run(cache: nil, &block) cached_value = cache_read(cache) # return cached unless cached.nil? return cached_value if !cached_value.nil? && !cache_should_refresh?(cache) current_status = status return run_skipped(cached_value) unless current_status.can_run? run_exec(current_status, cached_value, cache, &block) end |
#status ⇒ Status
Get the current status of the circuit
This method is not safe for concurrent operations, so it's unsafe to check this method and make runtime decisions based on that. However, it's useful for getting a non-synchronized snapshot of a circuit.
371 372 373 |
# File 'lib/faulty/circuit.rb', line 371 def status storage.status(self) end |
#try_run(**options) { ... } ⇒ Result<Object, Error>
281 282 283 284 285 |
# File 'lib/faulty/circuit.rb', line 281 def try_run(**, &block) Result.new(ok: run(**, &block)) rescue FaultyError => e Result.new(error: e) end |
#unlock! ⇒ self
Remove any open or closed locks
347 348 349 350 |
# File 'lib/faulty/circuit.rb', line 347 def unlock! storage.unlock(self) self end |