faulty 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +56 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile +11 -3
- data/README.md +919 -303
- data/bin/check-version +5 -1
- data/faulty.gemspec +1 -2
- data/lib/faulty.rb +35 -23
- data/lib/faulty/cache.rb +2 -0
- data/lib/faulty/cache/auto_wire.rb +58 -0
- data/lib/faulty/cache/circuit_proxy.rb +61 -0
- data/lib/faulty/cache/default.rb +9 -20
- data/lib/faulty/cache/fault_tolerant_proxy.rb +13 -2
- data/lib/faulty/cache/rails.rb +8 -9
- data/lib/faulty/circuit.rb +30 -15
- data/lib/faulty/error.rb +25 -3
- data/lib/faulty/events/log_listener.rb +4 -5
- data/lib/faulty/patch.rb +154 -0
- data/lib/faulty/patch/base.rb +46 -0
- data/lib/faulty/patch/mysql2.rb +81 -0
- data/lib/faulty/patch/redis.rb +93 -0
- data/lib/faulty/status.rb +2 -1
- data/lib/faulty/storage.rb +3 -0
- data/lib/faulty/storage/auto_wire.rb +107 -0
- data/lib/faulty/storage/circuit_proxy.rb +64 -0
- data/lib/faulty/storage/fallback_chain.rb +207 -0
- data/lib/faulty/storage/fault_tolerant_proxy.rb +50 -55
- data/lib/faulty/storage/interface.rb +2 -1
- data/lib/faulty/storage/memory.rb +7 -3
- data/lib/faulty/storage/redis.rb +69 -7
- data/lib/faulty/version.rb +1 -1
- metadata +15 -20
- data/.travis.yml +0 -46
data/bin/check-version
CHANGED
@@ -3,8 +3,12 @@
|
|
3
3
|
set -e
|
4
4
|
|
5
5
|
tag="$(git describe --abbrev=0 2>/dev/null || echo)"
|
6
|
+
echo "Tag: ${tag}"
|
6
7
|
tag="${tag#v}"
|
8
|
+
echo "Git Version: ${tag}"
|
7
9
|
[ "$tag" = '' ] && exit 0
|
10
|
+
gem_version="$(ruby -r ./lib/faulty/version -e "puts Faulty.version" | tail -n1)"
|
11
|
+
echo "Gem Version: ${gem_version}"
|
8
12
|
|
9
|
-
tag_gt_version
|
13
|
+
tag_gt_version="$(ruby -r ./lib/faulty/version -e "puts Faulty.version >= Gem::Version.new('${tag}')" | tail -n1)"
|
10
14
|
test "$tag_gt_version" = true
|
data/faulty.gemspec
CHANGED
@@ -26,8 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
# Only essential development tools and dependencies go here.
|
27
27
|
# Other non-essential development dependencies go in the Gemfile.
|
28
28
|
spec.add_development_dependency 'connection_pool', '~> 2.0'
|
29
|
-
spec.add_development_dependency '
|
30
|
-
spec.add_development_dependency 'redis', '~> 3.0'
|
29
|
+
spec.add_development_dependency 'redis', '>= 3.0'
|
31
30
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
32
31
|
# 0.81 is the last rubocop version with Ruby 2.3 support
|
33
32
|
spec.add_development_dependency 'rubocop', '0.81.0'
|
data/lib/faulty.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'securerandom'
|
4
|
+
require 'forwardable'
|
4
5
|
require 'concurrent-ruby'
|
5
6
|
|
6
7
|
require 'faulty/immutable_options'
|
@@ -8,6 +9,7 @@ require 'faulty/cache'
|
|
8
9
|
require 'faulty/circuit'
|
9
10
|
require 'faulty/error'
|
10
11
|
require 'faulty/events'
|
12
|
+
require 'faulty/patch'
|
11
13
|
require 'faulty/result'
|
12
14
|
require 'faulty/status'
|
13
15
|
require 'faulty/storage'
|
@@ -65,7 +67,7 @@ class Faulty
|
|
65
67
|
def [](name)
|
66
68
|
raise UninitializedError unless @instances
|
67
69
|
|
68
|
-
@instances[name]
|
70
|
+
@instances[name.to_s]
|
69
71
|
end
|
70
72
|
|
71
73
|
# Register an instance to the global Faulty state
|
@@ -74,13 +76,22 @@ class Faulty
|
|
74
76
|
# return value if you need to know whether the instance already existed.
|
75
77
|
#
|
76
78
|
# @param name [Symbol] The name of the instance to register
|
77
|
-
# @param instance [Faulty] The instance to register
|
79
|
+
# @param instance [Faulty] The instance to register. If nil, a new instance
|
80
|
+
# will be created from the given options or block.
|
81
|
+
# @param config [Hash] Attributes for {Faulty::Options}
|
82
|
+
# @yield [Faulty::Options] For setting options in a block
|
78
83
|
# @return [Faulty, nil] The previously-registered instance of that name if
|
79
84
|
# it already existed, otherwise nil.
|
80
|
-
def register(name, instance)
|
85
|
+
def register(name, instance = nil, **config, &block)
|
81
86
|
raise UninitializedError unless @instances
|
82
87
|
|
83
|
-
|
88
|
+
if instance
|
89
|
+
raise ArgumentError, 'Do not give config options if an instance is given' if !config.empty? || block
|
90
|
+
else
|
91
|
+
instance = new(**config, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
@instances.put_if_absent(name.to_s, instance)
|
84
95
|
end
|
85
96
|
|
86
97
|
# Get the options for the default instance
|
@@ -124,20 +135,28 @@ class Faulty
|
|
124
135
|
# Options for {Faulty}
|
125
136
|
#
|
126
137
|
# @!attribute [r] cache
|
138
|
+
# @see Cache::AutoWire
|
127
139
|
# @return [Cache::Interface] A cache backend if you want
|
128
140
|
# to use Faulty's cache support. Automatically wrapped in a
|
129
|
-
# {Cache::
|
141
|
+
# {Cache::AutoWire}. Default `Cache::AutoWire.new`.
|
142
|
+
# @!attribute [r] circuit_defaults
|
143
|
+
# @see Circuit::Options
|
144
|
+
# @return [Hash] A hash of default options to be used when creating
|
145
|
+
# new circuits. See {Circuit::Options} for a full list.
|
130
146
|
# @!attribute [r] storage
|
131
|
-
# @
|
132
|
-
#
|
133
|
-
#
|
147
|
+
# @see Storage::AutoWire
|
148
|
+
# @return [Storage::Interface, Array<Storage::Interface>] The storage
|
149
|
+
# backend. Automatically wrapped in a {Storage::AutoWire}, so this can also
|
150
|
+
# be given an array of prioritized backends. Default `Storage::AutoWire.new`.
|
134
151
|
# @!attribute [r] listeners
|
152
|
+
# @see Events::ListenerInterface
|
135
153
|
# @return [Array] listeners Faulty event listeners
|
136
154
|
# @!attribute [r] notifier
|
137
155
|
# @return [Events::Notifier] A Faulty notifier. If given, listeners are
|
138
156
|
# ignored.
|
139
157
|
Options = Struct.new(
|
140
158
|
:cache,
|
159
|
+
:circuit_defaults,
|
141
160
|
:storage,
|
142
161
|
:listeners,
|
143
162
|
:notifier
|
@@ -148,24 +167,17 @@ class Faulty
|
|
148
167
|
|
149
168
|
def finalize
|
150
169
|
self.notifier ||= Events::Notifier.new(listeners || [])
|
151
|
-
|
152
|
-
self.
|
153
|
-
unless storage.fault_tolerant?
|
154
|
-
self.storage = Storage::FaultTolerantProxy.new(storage, notifier: notifier)
|
155
|
-
end
|
156
|
-
|
157
|
-
self.cache ||= Cache::Default.new
|
158
|
-
unless cache.fault_tolerant?
|
159
|
-
self.cache = Cache::FaultTolerantProxy.new(cache, notifier: notifier)
|
160
|
-
end
|
170
|
+
self.storage = Storage::AutoWire.wrap(storage, notifier: notifier)
|
171
|
+
self.cache = Cache::AutoWire.wrap(cache, notifier: notifier)
|
161
172
|
end
|
162
173
|
|
163
174
|
def required
|
164
|
-
%i[cache storage notifier]
|
175
|
+
%i[cache circuit_defaults storage notifier]
|
165
176
|
end
|
166
177
|
|
167
178
|
def defaults
|
168
179
|
{
|
180
|
+
circuit_defaults: {},
|
169
181
|
listeners: [Events::LogListener.new]
|
170
182
|
}
|
171
183
|
end
|
@@ -204,8 +216,8 @@ class Faulty
|
|
204
216
|
# @return [Circuit] The new circuit or the existing circuit if it already exists
|
205
217
|
def circuit(name, **options, &block)
|
206
218
|
name = name.to_s
|
207
|
-
options = options.merge(circuit_options)
|
208
219
|
@circuits.compute_if_absent(name) do
|
220
|
+
options = circuit_options.merge(options)
|
209
221
|
Circuit.new(name, **options, &block)
|
210
222
|
end
|
211
223
|
end
|
@@ -223,8 +235,8 @@ class Faulty
|
|
223
235
|
#
|
224
236
|
# @return [Hash] The circuit options
|
225
237
|
def circuit_options
|
226
|
-
|
227
|
-
|
228
|
-
|
238
|
+
@options.to_h
|
239
|
+
.select { |k, _v| %i[cache storage notifier].include?(k) }
|
240
|
+
.merge(options.circuit_defaults)
|
229
241
|
end
|
230
242
|
end
|
data/lib/faulty/cache.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Faulty
|
4
|
+
module Cache
|
5
|
+
# Automatically configure a cache backend
|
6
|
+
#
|
7
|
+
# Used by {Faulty#initialize} to setup sensible cache defaults
|
8
|
+
class AutoWire
|
9
|
+
# Options for {AutoWire}
|
10
|
+
#
|
11
|
+
# @!attribute [r] circuit
|
12
|
+
# @return [Circuit] A circuit for {CircuitProxy} if one is created.
|
13
|
+
# When modifying this, be careful to use only a reliable circuit
|
14
|
+
# storage backend so that you don't introduce cascading failures.
|
15
|
+
# @!attribute [r] notifier
|
16
|
+
# @return [Events::Notifier] A Faulty notifier. If given, listeners are
|
17
|
+
# ignored.
|
18
|
+
Options = Struct.new(
|
19
|
+
:circuit,
|
20
|
+
:notifier
|
21
|
+
) do
|
22
|
+
include ImmutableOptions
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def required
|
27
|
+
%i[notifier]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Wrap a cache backend with sensible defaults
|
33
|
+
#
|
34
|
+
# If the cache is `nil`, create a new {Default}.
|
35
|
+
#
|
36
|
+
# If the backend is not fault tolerant, wrap it in {CircuitProxy} and
|
37
|
+
# {FaultTolerantProxy}.
|
38
|
+
#
|
39
|
+
# @param cache [Interface] A cache backend
|
40
|
+
# @param options [Hash] Attributes for {Options}
|
41
|
+
# @yield [Options] For setting options in a block
|
42
|
+
def wrap(cache, **options, &block)
|
43
|
+
options = Options.new(options, &block)
|
44
|
+
if cache.nil?
|
45
|
+
Cache::Default.new
|
46
|
+
elsif cache.fault_tolerant?
|
47
|
+
cache
|
48
|
+
else
|
49
|
+
Cache::FaultTolerantProxy.new(
|
50
|
+
Cache::CircuitProxy.new(cache, circuit: options.circuit, notifier: options.notifier),
|
51
|
+
notifier: options.notifier
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Faulty
|
4
|
+
module Cache
|
5
|
+
# A circuit wrapper for cache backends
|
6
|
+
#
|
7
|
+
# This class uses an internal {Circuit} to prevent the cache backend from
|
8
|
+
# causing application issues. If the backend fails continuously, this
|
9
|
+
# circuit will trip to prevent cascading failures. This internal circuit
|
10
|
+
# uses an independent in-memory backend by default.
|
11
|
+
class CircuitProxy
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# Options for {CircuitProxy}
|
15
|
+
#
|
16
|
+
# @!attribute [r] circuit
|
17
|
+
# @return [Circuit] A replacement for the internal circuit. When
|
18
|
+
# modifying this, be careful to use only a reliable circuit storage
|
19
|
+
# backend so that you don't introduce cascading failures.
|
20
|
+
# @!attribute [r] notifier
|
21
|
+
# @return [Events::Notifier] A Faulty notifier to use for failure
|
22
|
+
# notifications. If `circuit` is given, this is ignored.
|
23
|
+
Options = Struct.new(
|
24
|
+
:circuit,
|
25
|
+
:notifier
|
26
|
+
) do
|
27
|
+
include ImmutableOptions
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def finalize
|
32
|
+
raise ArgumentError, 'The circuit or notifier option must be given' unless notifier || circuit
|
33
|
+
|
34
|
+
self.circuit ||= Circuit.new(
|
35
|
+
Faulty::Storage::CircuitProxy.name,
|
36
|
+
notifier: notifier,
|
37
|
+
cache: Cache::Null.new
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param cache [Cache::Interface] The cache backend to wrap
|
43
|
+
# @param options [Hash] Attributes for {Options}
|
44
|
+
# @yield [Options] For setting options in a block
|
45
|
+
def initialize(cache, **options, &block)
|
46
|
+
@cache = cache
|
47
|
+
@options = Options.new(options, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
%i[read write].each do |method|
|
51
|
+
define_method(method) do |*args|
|
52
|
+
options.circuit.run { @cache.public_send(method, *args) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fault_tolerant?
|
57
|
+
@cache.fault_tolerant?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/faulty/cache/default.rb
CHANGED
@@ -11,6 +11,8 @@ class Faulty
|
|
11
11
|
# - If ActiveSupport is available, it will use an `ActiveSupport::Cache::MemoryStore`
|
12
12
|
# - Otherwise it will use a {Faulty::Cache::Null}
|
13
13
|
class Default
|
14
|
+
extend Forwardable
|
15
|
+
|
14
16
|
def initialize
|
15
17
|
@cache = if defined?(::Rails)
|
16
18
|
Cache::Rails.new(::Rails.cache)
|
@@ -21,28 +23,15 @@ class Faulty
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
#
|
26
|
+
# @!method read(key)
|
27
|
+
# (see Faulty::Cache::Interface#read)
|
25
28
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
def read(key)
|
29
|
-
@cache.read(key)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Write to the internal cache
|
29
|
+
# @!method write(key, value, expires_in: expires_in)
|
30
|
+
# (see Faulty::Cache::Interface#write)
|
33
31
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
|
37
|
-
@cache.write(key, value, expires_in: expires_in)
|
38
|
-
end
|
39
|
-
|
40
|
-
# This cache is fault tolerant if the internal one is
|
41
|
-
#
|
42
|
-
# @return [Boolean]
|
43
|
-
def fault_tolerant?
|
44
|
-
@cache.fault_tolerant?
|
45
|
-
end
|
32
|
+
# @!method fault_tolerant
|
33
|
+
# (see Faulty::Cache::Interface#fault_tolerant?)
|
34
|
+
def_delegators :@cache, :read, :write, :fault_tolerant?
|
46
35
|
end
|
47
36
|
end
|
48
37
|
end
|
@@ -8,7 +8,8 @@ class Faulty
|
|
8
8
|
# this class.
|
9
9
|
#
|
10
10
|
# If the cache backend raises a `StandardError`, it will be captured and
|
11
|
-
# sent to the notifier.
|
11
|
+
# sent to the notifier. Reads errors will return `nil`, and writes will be
|
12
|
+
# a no-op.
|
12
13
|
class FaultTolerantProxy
|
13
14
|
attr_reader :options
|
14
15
|
|
@@ -36,6 +37,16 @@ class Faulty
|
|
36
37
|
@options = Options.new(options, &block)
|
37
38
|
end
|
38
39
|
|
40
|
+
# Wrap a cache in a FaultTolerantProxy unless it's already fault tolerant
|
41
|
+
#
|
42
|
+
# @param cache [Cache::Interface] The cache to maybe wrap
|
43
|
+
# @return [Cache::Interface] The original cache or a {FaultTolerantProxy}
|
44
|
+
def self.wrap(cache, **options, &block)
|
45
|
+
return cache if cache.fault_tolerant?
|
46
|
+
|
47
|
+
new(cache, **options, &block)
|
48
|
+
end
|
49
|
+
|
39
50
|
# Read from the cache safely
|
40
51
|
#
|
41
52
|
# If the backend raises a `StandardError`, this will return `nil`.
|
@@ -58,7 +69,7 @@ class Faulty
|
|
58
69
|
# @return [void]
|
59
70
|
def write(key, value, expires_in: nil)
|
60
71
|
@cache.write(key, value, expires_in: expires_in)
|
61
|
-
rescue StandardError
|
72
|
+
rescue StandardError => e
|
62
73
|
options.notifier.notify(:cache_failure, key: key, action: :write, error: e)
|
63
74
|
nil
|
64
75
|
end
|
data/lib/faulty/cache/rails.rb
CHANGED
@@ -5,6 +5,8 @@ class Faulty
|
|
5
5
|
# A wrapper for a Rails or ActiveSupport cache
|
6
6
|
#
|
7
7
|
class Rails
|
8
|
+
extend Forwardable
|
9
|
+
|
8
10
|
# @param cache The Rails cache to wrap
|
9
11
|
# @param fault_tolerant [Boolean] Whether the Rails cache is
|
10
12
|
# fault_tolerant. See {#fault_tolerant?} for more details
|
@@ -13,15 +15,12 @@ class Faulty
|
|
13
15
|
@fault_tolerant = fault_tolerant
|
14
16
|
end
|
15
17
|
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def write(key, value, expires_in: nil)
|
23
|
-
@cache.write(key, value, expires_in: expires_in)
|
24
|
-
end
|
18
|
+
# @!method read(key)
|
19
|
+
# (see Faulty::Cache::Interface#read)
|
20
|
+
#
|
21
|
+
# @!method write(key, value, expires_in: expires_in)
|
22
|
+
# (see Faulty::Cache::Interface#write)
|
23
|
+
def_delegators :@cache, :read, :write
|
25
24
|
|
26
25
|
# Although ActiveSupport cache implementations are fault-tolerant,
|
27
26
|
# Rails.cache is not guranteed to be fault tolerant. For this reason,
|
data/lib/faulty/circuit.rb
CHANGED
@@ -50,6 +50,9 @@ class Faulty
|
|
50
50
|
# @!attribute [r] cool_down
|
51
51
|
# @return [Integer] The number of seconds the circuit will
|
52
52
|
# stay open after it is tripped. Default 300.
|
53
|
+
# @!attribute [r] error_module
|
54
|
+
# @return [Module] Used by patches to set the namespace module for
|
55
|
+
# the faulty errors that will be raised. Default `Faulty`
|
53
56
|
# @!attribute [r] evaluation_window
|
54
57
|
# @return [Integer] The number of seconds of history that
|
55
58
|
# will be evaluated to determine the failure rate for a circuit.
|
@@ -66,14 +69,19 @@ class Faulty
|
|
66
69
|
# @return [Error, Array<Error>] An array of errors that are considered circuit
|
67
70
|
# failures. Default `[StandardError]`.
|
68
71
|
# @!attribute [r] exclude
|
69
|
-
# @return [Error, Array<Error>] An array of errors that will be
|
70
|
-
#
|
72
|
+
# @return [Error, Array<Error>] An array of errors that will not be
|
73
|
+
# captured by Faulty. These errors will not be considered circuit
|
74
|
+
# failures. Default `[]`.
|
71
75
|
# @!attribute [r] cache
|
72
|
-
# @return [Cache::Interface] The cache backend. Default
|
76
|
+
# @return [Cache::Interface] The cache backend. Default
|
77
|
+
# `Cache::Null.new`. Unlike {Faulty#initialize}, this is not wrapped in
|
78
|
+
# {Cache::AutoWire} by default.
|
73
79
|
# @!attribute [r] notifier
|
74
80
|
# @return [Events::Notifier] A Faulty notifier. Default `Events::Notifier.new`
|
75
81
|
# @!attribute [r] storage
|
76
|
-
# @return [Storage::Interface] The storage backend. Default
|
82
|
+
# @return [Storage::Interface] The storage backend. Default
|
83
|
+
# `Storage::Memory.new`. Unlike {Faulty#initialize}, this is not wrapped
|
84
|
+
# in {Storage::AutoWire} by default.
|
77
85
|
Options = Struct.new(
|
78
86
|
:cache_expires_in,
|
79
87
|
:cache_refreshes_after,
|
@@ -83,6 +91,7 @@ class Faulty
|
|
83
91
|
:rate_threshold,
|
84
92
|
:sample_threshold,
|
85
93
|
:errors,
|
94
|
+
:error_module,
|
86
95
|
:exclude,
|
87
96
|
:cache,
|
88
97
|
:notifier,
|
@@ -98,6 +107,7 @@ class Faulty
|
|
98
107
|
cache_refreshes_after: 900,
|
99
108
|
cool_down: 300,
|
100
109
|
errors: [StandardError],
|
110
|
+
error_module: Faulty,
|
101
111
|
exclude: [],
|
102
112
|
evaluation_window: 60,
|
103
113
|
rate_threshold: 0.5,
|
@@ -110,6 +120,7 @@ class Faulty
|
|
110
120
|
cache
|
111
121
|
cool_down
|
112
122
|
errors
|
123
|
+
error_module
|
113
124
|
exclude
|
114
125
|
evaluation_window
|
115
126
|
rate_threshold
|
@@ -208,9 +219,11 @@ class Faulty
|
|
208
219
|
cached_value = cache_read(cache)
|
209
220
|
# return cached unless cached.nil?
|
210
221
|
return cached_value if !cached_value.nil? && !cache_should_refresh?(cache)
|
211
|
-
return run_skipped(cached_value) unless status.can_run?
|
212
222
|
|
213
|
-
|
223
|
+
current_status = status
|
224
|
+
return run_skipped(cached_value) unless current_status.can_run?
|
225
|
+
|
226
|
+
run_exec(current_status, cached_value, cache, &block)
|
214
227
|
end
|
215
228
|
|
216
229
|
# Force the circuit to stay open until unlocked
|
@@ -277,7 +290,7 @@ class Faulty
|
|
277
290
|
# @return The result from cache if available
|
278
291
|
def run_skipped(cached_value)
|
279
292
|
skipped!
|
280
|
-
raise OpenCircuitError.new(nil, self) if cached_value.nil?
|
293
|
+
raise options.error_module::OpenCircuitError.new(nil, self) if cached_value.nil?
|
281
294
|
|
282
295
|
cached_value
|
283
296
|
end
|
@@ -287,26 +300,27 @@ class Faulty
|
|
287
300
|
# @param cached_value The cached value if one is available
|
288
301
|
# @param cache_key [String, nil] The cache key if one is given
|
289
302
|
# @return The run result
|
290
|
-
def run_exec(cached_value, cache_key)
|
303
|
+
def run_exec(status, cached_value, cache_key)
|
291
304
|
result = yield
|
292
|
-
success!
|
305
|
+
success!(status)
|
293
306
|
cache_write(cache_key, result)
|
294
307
|
result
|
295
308
|
rescue *options.errors => e
|
296
309
|
raise if options.exclude.any? { |ex| e.is_a?(ex) }
|
297
310
|
|
298
311
|
if cached_value.nil?
|
299
|
-
raise CircuitTrippedError.new(nil, self) if failure!(e)
|
312
|
+
raise options.error_module::CircuitTrippedError.new(nil, self) if failure!(status, e)
|
300
313
|
|
301
|
-
raise CircuitFailureError.new(nil, self)
|
314
|
+
raise options.error_module::CircuitFailureError.new(nil, self)
|
302
315
|
else
|
303
316
|
cached_value
|
304
317
|
end
|
305
318
|
end
|
306
319
|
|
307
320
|
# @return [Boolean] True if the circuit transitioned to closed
|
308
|
-
def success!
|
309
|
-
|
321
|
+
def success!(status)
|
322
|
+
entries = storage.entry(self, Faulty.current_time, true)
|
323
|
+
status = Status.from_entries(entries, **status.to_h)
|
310
324
|
closed = false
|
311
325
|
closed = close! if should_close?(status)
|
312
326
|
|
@@ -315,8 +329,9 @@ class Faulty
|
|
315
329
|
end
|
316
330
|
|
317
331
|
# @return [Boolean] True if the circuit transitioned to open
|
318
|
-
def failure!(error)
|
319
|
-
|
332
|
+
def failure!(status, error)
|
333
|
+
entries = storage.entry(self, Faulty.current_time, false)
|
334
|
+
status = Status.from_entries(entries, **status.to_h)
|
320
335
|
options.notifier.notify(:circuit_failure, circuit: self, status: status, error: error)
|
321
336
|
|
322
337
|
opened = if status.half_open?
|