faulty 0.7.2 → 0.8.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/CHANGELOG.md +19 -0
- data/README.md +4 -4
- data/faulty.gemspec +1 -0
- data/lib/faulty/cache/auto_wire.rb +0 -2
- data/lib/faulty/cache/circuit_proxy.rb +0 -2
- data/lib/faulty/cache/fault_tolerant_proxy.rb +0 -2
- data/lib/faulty/circuit.rb +96 -5
- data/lib/faulty/circuit_registry.rb +49 -0
- data/lib/faulty/immutable_options.rb +21 -7
- data/lib/faulty/status.rb +0 -2
- data/lib/faulty/storage/auto_wire.rb +0 -2
- data/lib/faulty/storage/circuit_proxy.rb +14 -3
- data/lib/faulty/storage/fallback_chain.rb +18 -2
- data/lib/faulty/storage/fault_tolerant_proxy.rb +24 -2
- data/lib/faulty/storage/interface.rb +24 -1
- data/lib/faulty/storage/memory.rb +19 -3
- data/lib/faulty/storage/null.rb +11 -0
- data/lib/faulty/storage/redis.rb +35 -3
- data/lib/faulty/version.rb +1 -1
- data/lib/faulty.rb +3 -5
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 815cc1b7ac571e9dc69546efd1b3892795a5ef284cc43448b8a6c1feadcf49c3
|
4
|
+
data.tar.gz: 575c2e0e7abb413f8866a0e159129a6f1ecc76149e8d8cb97c65c7ffd9913ba9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6effebfa578717256dc4ef7ec1c59a5f694eff2c78b21edb1649375a3f7ab62bbad597e3e4a0dc63cd729b4e66b4079ea9cbfd72795a1bfd34a9fab489415847
|
7
|
+
data.tar.gz: 2ba730eaa396ce24cb9d4eaf3a35db84180eb5c8ba4e3f3bc803d389436870480bd542aa57c93089ef33b77afe3f9b0c8614f972d232b971d8310f2dd067eb39
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
## Release v0.8.0
|
2
|
+
|
3
|
+
* Store circuit options in the backend when run #34 justinhoward
|
4
|
+
|
5
|
+
### Breaking Changes
|
6
|
+
|
7
|
+
* Added #get_options and #set_options to Faulty::Storage::Interface.
|
8
|
+
These will need to be added to any custom backends
|
9
|
+
* Faulty::Storage::Interface#reset now requires removing options in
|
10
|
+
addition to other stored values
|
11
|
+
* Circuit options will now be supplemented by stored options until they
|
12
|
+
are run. This is technically a breaking change in behavior, although
|
13
|
+
in most cases this should cause the expected result.
|
14
|
+
* Circuits are not memoized until they are run. Subsequent calls
|
15
|
+
to Faulty#circuit can return different instances if the circuit is
|
16
|
+
not run. However, once run, options are synchronized between
|
17
|
+
instances, so likely this will not be a breaking change for most
|
18
|
+
cases.
|
19
|
+
|
1
20
|
## Release v0.7.2
|
2
21
|
|
3
22
|
* Add Faulty.disable! for disabling globally #38 justinhoward
|
data/README.md
CHANGED
@@ -608,7 +608,7 @@ faulty.circuit('standalone_circuit')
|
|
608
608
|
```
|
609
609
|
|
610
610
|
Calling `#circuit` on the instance still has the same memoization behavior that
|
611
|
-
`Faulty.circuit` has, so subsequent
|
611
|
+
`Faulty.circuit` has, so subsequent runs for the same circuit will use a
|
612
612
|
memoized circuit object.
|
613
613
|
|
614
614
|
|
@@ -733,7 +733,7 @@ Both options can even be specified together.
|
|
733
733
|
```ruby
|
734
734
|
Faulty.circuit(
|
735
735
|
'api',
|
736
|
-
errors: [ActiveRecord::ActiveRecordError]
|
736
|
+
errors: [ActiveRecord::ActiveRecordError],
|
737
737
|
exclude: [ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique]
|
738
738
|
).run do
|
739
739
|
# This only captures ActiveRecord::ActiveRecordError errors, but not
|
@@ -813,7 +813,7 @@ the options are retained within the context of each instance. All options given
|
|
813
813
|
after the first call to `Faulty.circuit` (or `Faulty#circuit`) are ignored.
|
814
814
|
|
815
815
|
```ruby
|
816
|
-
Faulty.circuit('api', rate_threshold: 0.7)
|
816
|
+
Faulty.circuit('api', rate_threshold: 0.7).run { api.call }
|
817
817
|
|
818
818
|
# These options are ignored since with already initialized the circuit
|
819
819
|
circuit = Faulty.circuit('api', rate_threshold: 0.3)
|
@@ -821,7 +821,7 @@ circuit.options.rate_threshold # => 0.7
|
|
821
821
|
```
|
822
822
|
|
823
823
|
This is because the circuit objects themselves are internally memoized, and are
|
824
|
-
read-only once
|
824
|
+
read-only once they are run.
|
825
825
|
|
826
826
|
The following example represents the defaults for a new circuit:
|
827
827
|
|
data/faulty.gemspec
CHANGED
@@ -26,6 +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 'json'
|
29
30
|
spec.add_development_dependency 'redis', '>= 3.0'
|
30
31
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
31
32
|
# 0.81 is the last rubocop version with Ruby 2.3 support
|
data/lib/faulty/circuit.rb
CHANGED
@@ -27,7 +27,6 @@ class Faulty
|
|
27
27
|
CACHE_REFRESH_SUFFIX = '.faulty_refresh'
|
28
28
|
|
29
29
|
attr_reader :name
|
30
|
-
attr_reader :options
|
31
30
|
|
32
31
|
# Options for {Circuit}
|
33
32
|
#
|
@@ -82,6 +81,9 @@ class Faulty
|
|
82
81
|
# @return [Storage::Interface] The storage backend. Default
|
83
82
|
# `Storage::Memory.new`. Unlike {Faulty#initialize}, this is not wrapped
|
84
83
|
# in {Storage::AutoWire} by default.
|
84
|
+
# @!attribute [r] registry
|
85
|
+
# @return [CircuitRegistry] For use by {Faulty} instances to facilitate
|
86
|
+
# memoization of circuits.
|
85
87
|
Options = Struct.new(
|
86
88
|
:cache_expires_in,
|
87
89
|
:cache_refreshes_after,
|
@@ -95,11 +97,22 @@ class Faulty
|
|
95
97
|
:exclude,
|
96
98
|
:cache,
|
97
99
|
:notifier,
|
98
|
-
:storage
|
100
|
+
:storage,
|
101
|
+
:registry
|
99
102
|
) do
|
100
103
|
include ImmutableOptions
|
101
104
|
|
102
|
-
|
105
|
+
# Get the options stored in the storage backend
|
106
|
+
#
|
107
|
+
# @return [Hash] A hash of stored options
|
108
|
+
def for_storage
|
109
|
+
{
|
110
|
+
cool_down: cool_down,
|
111
|
+
evaluation_window: evaluation_window,
|
112
|
+
rate_threshold: rate_threshold,
|
113
|
+
sample_threshold: sample_threshold
|
114
|
+
}
|
115
|
+
end
|
103
116
|
|
104
117
|
def defaults
|
105
118
|
{
|
@@ -150,7 +163,59 @@ class Faulty
|
|
150
163
|
raise ArgumentError, 'name must be a String' unless name.is_a?(String)
|
151
164
|
|
152
165
|
@name = name
|
153
|
-
@
|
166
|
+
@given_options = Options.new(options, &block)
|
167
|
+
@pulled_options = nil
|
168
|
+
@options_pushed = false
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get the options for this circuit
|
172
|
+
#
|
173
|
+
# If this circuit has been run, these will the options exactly as given
|
174
|
+
# to {.new}. However, if this circuit has not yet been run, these options
|
175
|
+
# will be supplemented by the last-known options from the circuit storage.
|
176
|
+
#
|
177
|
+
# Once a circuit is run, the given options are pushed to circuit storage to
|
178
|
+
# be persisted.
|
179
|
+
#
|
180
|
+
# This is to allow circuit objects to behave as expected in contexts where
|
181
|
+
# the exact options for a circuit are not known such as an admin dashboard
|
182
|
+
# or in a debug console.
|
183
|
+
#
|
184
|
+
# Note that this distinction isn't usually important unless using
|
185
|
+
# distributed circuit storage like the Redis storage backend.
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# Faulty.circuit('api', cool_down: 5).run { api.users }
|
189
|
+
# # This status will be calculated using the cool_down of 5 because
|
190
|
+
# # the circuit was already run
|
191
|
+
# Faulty.circuit('api').status
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
# # This status will be calculated using the cool_down in circuit storage
|
195
|
+
# # if it is available instead of using the default value.
|
196
|
+
# Faulty.circuit('api').status
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# # For typical usage, this behaves as expected, but note that it's
|
200
|
+
# # possible to run into some unexpected behavior when creating circuits
|
201
|
+
# # in unusual ways.
|
202
|
+
#
|
203
|
+
# # For example, this status will be calculated using the cool_down in
|
204
|
+
# # circuit storage if it is available despite the given value of 5.
|
205
|
+
# Faulty.circuit('api', cool_down: 5).status
|
206
|
+
# Faulty.circuit('api').run { api.users }
|
207
|
+
# # However now, after the circuit is run, status will be calculated
|
208
|
+
# # using the given cool_down of 5 and the value of 5 will be pushed
|
209
|
+
# # permanently to circuit storage
|
210
|
+
# Faulty.circuit('api').status
|
211
|
+
#
|
212
|
+
# @return [Options] The resolved options
|
213
|
+
def options
|
214
|
+
return @given_options if @options_pushed
|
215
|
+
return @pulled_options if @pulled_options
|
216
|
+
|
217
|
+
stored = @given_options.storage.get_options(self)
|
218
|
+
@pulled_options = stored ? @given_options.dup_with(stored) : @given_options
|
154
219
|
end
|
155
220
|
|
156
221
|
# Run the circuit as with {#run}, but return a {Result}
|
@@ -204,6 +269,8 @@ class Faulty
|
|
204
269
|
# a second cool down period. However, if the circuit completes successfully,
|
205
270
|
# the circuit will be closed and reset to its initial state.
|
206
271
|
#
|
272
|
+
# When this is run, the given options are persisted to the storage backend.
|
273
|
+
#
|
207
274
|
# @param cache [String, nil] A cache key, or nil if caching is not desired
|
208
275
|
# @yield The block to protect with this circuit
|
209
276
|
# @raise If the block raises an error not in the error list, or if the error
|
@@ -216,6 +283,7 @@ class Faulty
|
|
216
283
|
# circuit to trip
|
217
284
|
# @return The return value of the block
|
218
285
|
def run(cache: nil, &block)
|
286
|
+
push_options
|
219
287
|
cached_value = cache_read(cache)
|
220
288
|
# return cached unless cached.nil?
|
221
289
|
return cached_value if !cached_value.nil? && !cache_should_refresh?(cache)
|
@@ -256,6 +324,8 @@ class Faulty
|
|
256
324
|
#
|
257
325
|
# @return [self]
|
258
326
|
def reset!
|
327
|
+
@options_pushed = false
|
328
|
+
@pulled_options = nil
|
259
329
|
storage.reset(self)
|
260
330
|
self
|
261
331
|
end
|
@@ -284,6 +354,25 @@ class Faulty
|
|
284
354
|
|
285
355
|
private
|
286
356
|
|
357
|
+
# Push the given options to circuit storage and set those as the current
|
358
|
+
# options
|
359
|
+
#
|
360
|
+
# @return [void]
|
361
|
+
def push_options
|
362
|
+
return if @options_pushed
|
363
|
+
|
364
|
+
@pulled_options = nil
|
365
|
+
@options_pushed = true
|
366
|
+
resolved = options.registry&.resolve(self)
|
367
|
+
if resolved
|
368
|
+
# If another circuit instance was resolved, don't store these options
|
369
|
+
# Instead, copy the options from that circuit as if we were given those
|
370
|
+
@given_options = resolved.options
|
371
|
+
else
|
372
|
+
storage.set_options(self, @given_options.for_storage)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
287
376
|
# Process a skipped run
|
288
377
|
#
|
289
378
|
# @param cached_value The cached value if one is available
|
@@ -431,11 +520,13 @@ class Faulty
|
|
431
520
|
|
432
521
|
# Alias to the storage engine from options
|
433
522
|
#
|
523
|
+
# Always returns the value from the given options
|
524
|
+
#
|
434
525
|
# @return [Storage::Interface]
|
435
526
|
def storage
|
436
527
|
return Faulty::Storage::Null.new if Faulty.disabled?
|
437
528
|
|
438
|
-
|
529
|
+
@given_options.storage
|
439
530
|
end
|
440
531
|
end
|
441
532
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Faulty
|
4
|
+
# Used by Faulty instances to track and memoize Circuits
|
5
|
+
#
|
6
|
+
# Whenever a circuit is requested by `Faulty#circuit`, it calls
|
7
|
+
# `#retrieve`. That will return a resolved circuit if there is one, or
|
8
|
+
# otherwise, it will create a new circuit instance.
|
9
|
+
#
|
10
|
+
# Once any circuit is run, the circuit calls `#resolve`. That saves
|
11
|
+
# the instance into the registry. Any calls to `#retrieve` after
|
12
|
+
# the circuit is resolved will result in the same instance being returned.
|
13
|
+
#
|
14
|
+
# However, before a circuit is resolved, calling `Faulty#circuit` will result
|
15
|
+
# in a new Circuit instance being created for every call. If multiples of
|
16
|
+
# these call `resolve`, only the first one will "win" and be memoized.
|
17
|
+
class CircuitRegistry
|
18
|
+
def initialize(circuit_options)
|
19
|
+
@circuit_options = circuit_options
|
20
|
+
@circuit_options[:registry] = self
|
21
|
+
@circuits = Concurrent::Map.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve a memoized circuit with the same name, or if none is yet
|
25
|
+
# resolved, create a new one.
|
26
|
+
#
|
27
|
+
# @param name [String] The name of the circuit
|
28
|
+
# @param options [Hash] Options for {Circuit::Options}
|
29
|
+
# @yield [Circuit::Options] For setting options in a block
|
30
|
+
# @return [Circuit] The new or memoized circuit
|
31
|
+
def retrieve(name, options, &block)
|
32
|
+
@circuits.fetch(name) do
|
33
|
+
options = @circuit_options.merge(options)
|
34
|
+
Circuit.new(name, **options, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Save and memoize the given circuit as the "canonical" instance for
|
39
|
+
# the circuit name
|
40
|
+
#
|
41
|
+
# If the name is already resolved, this will be ignored
|
42
|
+
#
|
43
|
+
# @return [Circuit, nil] If this circuit name is already resolved, the
|
44
|
+
# already-resolved circuit
|
45
|
+
def resolve(circuit)
|
46
|
+
@circuits.put_if_absent(circuit.name, circuit)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -5,18 +5,23 @@ class Faulty
|
|
5
5
|
module ImmutableOptions
|
6
6
|
# @param hash [Hash] A hash of attributes to initialize with
|
7
7
|
# @yield [self] Yields itself to the block to set options before freezing
|
8
|
-
def initialize(hash)
|
9
|
-
defaults.merge(hash)
|
8
|
+
def initialize(hash, &block)
|
9
|
+
setup(defaults.merge(hash), &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def dup_with(hash, &block)
|
13
|
+
dup.setup(hash, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup(hash)
|
17
|
+
hash&.each { |key, value| self[key] = value }
|
10
18
|
yield self if block_given?
|
11
19
|
finalize
|
12
|
-
|
13
|
-
raise ArgumentError, "Missing required attribute #{key}" if self[key].nil?
|
14
|
-
end
|
20
|
+
guard_required!
|
15
21
|
freeze
|
22
|
+
self
|
16
23
|
end
|
17
24
|
|
18
|
-
private
|
19
|
-
|
20
25
|
# A hash of default values to set before yielding to the block
|
21
26
|
#
|
22
27
|
# @return [Hash<Symbol, Object>]
|
@@ -36,5 +41,14 @@ class Faulty
|
|
36
41
|
# @return [void]
|
37
42
|
def finalize
|
38
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Raise an error if required options are missing
|
48
|
+
def guard_required!
|
49
|
+
required.each do |key|
|
50
|
+
raise ArgumentError, "Missing required attribute #{key}" if self[key].nil?
|
51
|
+
end
|
52
|
+
end
|
39
53
|
end
|
40
54
|
end
|
data/lib/faulty/status.rb
CHANGED
@@ -26,8 +26,6 @@ class Faulty
|
|
26
26
|
) do
|
27
27
|
include ImmutableOptions
|
28
28
|
|
29
|
-
private
|
30
|
-
|
31
29
|
def finalize
|
32
30
|
raise ArgumentError, 'The circuit or notifier option must be given' unless notifier || circuit
|
33
31
|
|
@@ -47,7 +45,20 @@ class Faulty
|
|
47
45
|
@options = Options.new(options, &block)
|
48
46
|
end
|
49
47
|
|
50
|
-
%i[
|
48
|
+
%i[
|
49
|
+
get_options
|
50
|
+
set_options
|
51
|
+
entry
|
52
|
+
open
|
53
|
+
reopen
|
54
|
+
close
|
55
|
+
lock
|
56
|
+
unlock
|
57
|
+
reset
|
58
|
+
status
|
59
|
+
history
|
60
|
+
list
|
61
|
+
].each do |method|
|
51
62
|
define_method(method) do |*args|
|
52
63
|
options.circuit.run { @storage.public_send(method, *args) }
|
53
64
|
end
|
@@ -30,8 +30,6 @@ class Faulty
|
|
30
30
|
) do
|
31
31
|
include ImmutableOptions
|
32
32
|
|
33
|
-
private
|
34
|
-
|
35
33
|
def required
|
36
34
|
%i[notifier]
|
37
35
|
end
|
@@ -49,6 +47,24 @@ class Faulty
|
|
49
47
|
@options = Options.new(options, &block)
|
50
48
|
end
|
51
49
|
|
50
|
+
# Get options from the first available storage backend
|
51
|
+
#
|
52
|
+
# @param (see Interface#get_options)
|
53
|
+
# @return (see Interface#get_options)
|
54
|
+
def get_options(circuit)
|
55
|
+
send_chain(:get_options, circuit) do |e|
|
56
|
+
options.notifier.notify(:storage_failure, circuit: circuit, action: :get_options, error: e)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Try to set circuit options on all backends
|
61
|
+
#
|
62
|
+
# @param (see Interface#set_options)
|
63
|
+
# @return (see Interface#set_options)
|
64
|
+
def set_options(circuit, stored_options)
|
65
|
+
send_all(:set_options, circuit, stored_options)
|
66
|
+
end
|
67
|
+
|
52
68
|
# Create a circuit entry in the first available storage backend
|
53
69
|
#
|
54
70
|
# @param (see Interface#entry)
|
@@ -23,8 +23,6 @@ class Faulty
|
|
23
23
|
) do
|
24
24
|
include ImmutableOptions
|
25
25
|
|
26
|
-
private
|
27
|
-
|
28
26
|
def required
|
29
27
|
%i[notifier]
|
30
28
|
end
|
@@ -85,6 +83,30 @@ class Faulty
|
|
85
83
|
# @return (see Interface#list)
|
86
84
|
def_delegators :@storage, :lock, :unlock, :reset, :history, :list
|
87
85
|
|
86
|
+
# Get circuit options safely
|
87
|
+
#
|
88
|
+
# @see Interface#get_options
|
89
|
+
# @param (see Interface#get_options)
|
90
|
+
# @return (see Interface#get_options)
|
91
|
+
def get_options(circuit)
|
92
|
+
@storage.get_options(circuit)
|
93
|
+
rescue StandardError => e
|
94
|
+
options.notifier.notify(:storage_failure, circuit: circuit, action: :get_options, error: e)
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set circuit options safely
|
99
|
+
#
|
100
|
+
# @see Interface#get_options
|
101
|
+
# @param (see Interface#set_options)
|
102
|
+
# @return (see Interface#set_options)
|
103
|
+
def set_options(circuit, stored_options)
|
104
|
+
@storage.set_options(circuit, stored_options)
|
105
|
+
rescue StandardError => e
|
106
|
+
options.notifier.notify(:storage_failure, circuit: circuit, action: :set_options, error: e)
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
88
110
|
# Add a history entry safely
|
89
111
|
#
|
90
112
|
# @see Interface#entry
|
@@ -6,6 +6,29 @@ class Faulty
|
|
6
6
|
#
|
7
7
|
# This is for documentation only and is not loaded
|
8
8
|
class Interface
|
9
|
+
# Get the options stored for circuit
|
10
|
+
#
|
11
|
+
# They should be returned exactly as given by {#set_options}
|
12
|
+
#
|
13
|
+
# @return [Hash] A hash of the options stored by {#set_options}. The keys
|
14
|
+
# must be symbols.
|
15
|
+
def get_options(circuit)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
# Store the options for a circuit
|
20
|
+
#
|
21
|
+
# They should be returned exactly as given by {#set_options}
|
22
|
+
#
|
23
|
+
# @param circuit [Circuit] The circuit to set options for
|
24
|
+
# @param options [Hash<Symbol, Object>] A hash of symbol option names to
|
25
|
+
# circuit options. These option values are guranteed to be primive
|
26
|
+
# values.
|
27
|
+
# @return [void]
|
28
|
+
def set_options(circuit, stored_options)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
9
32
|
# Add a circuit run entry to storage
|
10
33
|
#
|
11
34
|
# The backend may choose to store this in whatever manner it chooses as
|
@@ -99,7 +122,7 @@ class Faulty
|
|
99
122
|
# Reset the circuit to a fresh state
|
100
123
|
#
|
101
124
|
# Clears all circuit status including entries, state, locks,
|
102
|
-
# opened_at, and any other values that would affect Status.
|
125
|
+
# opened_at, options, and any other values that would affect Status.
|
103
126
|
#
|
104
127
|
# No concurrency gurantees are provided for resetting
|
105
128
|
#
|
@@ -33,8 +33,6 @@ class Faulty
|
|
33
33
|
Options = Struct.new(:max_sample_size) do
|
34
34
|
include ImmutableOptions
|
35
35
|
|
36
|
-
private
|
37
|
-
|
38
36
|
def defaults
|
39
37
|
{ max_sample_size: 100 }
|
40
38
|
end
|
@@ -43,7 +41,7 @@ class Faulty
|
|
43
41
|
# The internal object for storing a circuit
|
44
42
|
#
|
45
43
|
# @private
|
46
|
-
MemoryCircuit = Struct.new(:state, :runs, :opened_at, :lock) do
|
44
|
+
MemoryCircuit = Struct.new(:state, :runs, :opened_at, :lock, :options) do
|
47
45
|
def initialize
|
48
46
|
self.state = Concurrent::Atom.new(:closed)
|
49
47
|
self.runs = Concurrent::MVar.new([], dup_on_deref: true)
|
@@ -78,6 +76,24 @@ class Faulty
|
|
78
76
|
@options = Options.new(options, &block)
|
79
77
|
end
|
80
78
|
|
79
|
+
# Get the options stored for circuit
|
80
|
+
#
|
81
|
+
# @see Interface#get_options
|
82
|
+
# @param (see Interface#get_options)
|
83
|
+
# @return (see Interface#get_options)
|
84
|
+
def get_options(circuit)
|
85
|
+
fetch(circuit).options
|
86
|
+
end
|
87
|
+
|
88
|
+
# Store the options for a circuit
|
89
|
+
#
|
90
|
+
# @see Interface#set_options
|
91
|
+
# @param (see Interface#set_options)
|
92
|
+
# @return (see Interface#set_options)
|
93
|
+
def set_options(circuit, stored_options)
|
94
|
+
fetch(circuit).options = stored_options
|
95
|
+
end
|
96
|
+
|
81
97
|
# Add an entry to storage
|
82
98
|
#
|
83
99
|
# @see Interface#entry
|
data/lib/faulty/storage/null.rb
CHANGED
@@ -11,6 +11,17 @@ class Faulty
|
|
11
11
|
@instance
|
12
12
|
end
|
13
13
|
|
14
|
+
# @param (see Interface#get_options)
|
15
|
+
# @return (see Interface#get_options)
|
16
|
+
def get_options(_circuit)
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param (see Interface#set_options)
|
21
|
+
# @return (see Interface#set_options)
|
22
|
+
def set_options(_circuit, _stored_options)
|
23
|
+
end
|
24
|
+
|
14
25
|
# @param (see Interface#entry)
|
15
26
|
# @return (see Interface#entry)
|
16
27
|
def entry(_circuit, _time, _success)
|
data/lib/faulty/storage/redis.rb
CHANGED
@@ -57,8 +57,6 @@ class Faulty
|
|
57
57
|
) do
|
58
58
|
include ImmutableOptions
|
59
59
|
|
60
|
-
private
|
61
|
-
|
62
60
|
def defaults
|
63
61
|
{
|
64
62
|
key_prefix: 'faulty',
|
@@ -85,9 +83,37 @@ class Faulty
|
|
85
83
|
def initialize(**options, &block)
|
86
84
|
@options = Options.new(options, &block)
|
87
85
|
|
86
|
+
# Ensure JSON is available since we don't explicitly require it
|
87
|
+
JSON # rubocop:disable Lint/Void
|
88
|
+
|
88
89
|
check_client_options!
|
89
90
|
end
|
90
91
|
|
92
|
+
# Get the options stored for circuit
|
93
|
+
#
|
94
|
+
# @see Interface#get_options
|
95
|
+
# @param (see Interface#get_options)
|
96
|
+
# @return (see Interface#get_options)
|
97
|
+
def get_options(circuit)
|
98
|
+
json = redis { |r| r.get(options_key(circuit)) }
|
99
|
+
return if json.nil?
|
100
|
+
|
101
|
+
JSON.parse(json, symbolize_names: true)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Store the options for a circuit
|
105
|
+
#
|
106
|
+
# These will be serialized as JSON
|
107
|
+
#
|
108
|
+
# @see Interface#set_options
|
109
|
+
# @param (see Interface#set_options)
|
110
|
+
# @return (see Interface#set_options)
|
111
|
+
def set_options(circuit, stored_options)
|
112
|
+
redis do |r|
|
113
|
+
r.set(options_key(circuit), JSON.dump(stored_options), ex: options.circuit_ttl)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
91
117
|
# Add an entry to storage
|
92
118
|
#
|
93
119
|
# @see Interface#entry
|
@@ -173,7 +199,8 @@ class Faulty
|
|
173
199
|
r.del(
|
174
200
|
entries_key(circuit),
|
175
201
|
opened_at_key(circuit),
|
176
|
-
lock_key(circuit)
|
202
|
+
lock_key(circuit),
|
203
|
+
options_key(circuit)
|
177
204
|
)
|
178
205
|
r.set(state_key(circuit), 'closed', ex: options.circuit_ttl)
|
179
206
|
end
|
@@ -239,6 +266,11 @@ class Faulty
|
|
239
266
|
key('circuit', circuit.name, *parts)
|
240
267
|
end
|
241
268
|
|
269
|
+
# @return [String] The key for circuit options
|
270
|
+
def options_key(circuit)
|
271
|
+
ckey(circuit, 'options')
|
272
|
+
end
|
273
|
+
|
242
274
|
# @return [String] The key for circuit state
|
243
275
|
def state_key(circuit)
|
244
276
|
ckey(circuit, 'state')
|
data/lib/faulty/version.rb
CHANGED
data/lib/faulty.rb
CHANGED
@@ -10,6 +10,7 @@ require 'faulty/circuit'
|
|
10
10
|
require 'faulty/error'
|
11
11
|
require 'faulty/events'
|
12
12
|
require 'faulty/patch'
|
13
|
+
require 'faulty/circuit_registry'
|
13
14
|
require 'faulty/result'
|
14
15
|
require 'faulty/status'
|
15
16
|
require 'faulty/storage'
|
@@ -226,8 +227,8 @@ class Faulty
|
|
226
227
|
# @param options [Hash] Attributes for {Options}
|
227
228
|
# @yield [Options] For setting options in a block
|
228
229
|
def initialize(**options, &block)
|
229
|
-
@circuits = Concurrent::Map.new
|
230
230
|
@options = Options.new(options, &block)
|
231
|
+
@registry = CircuitRegistry.new(circuit_options)
|
231
232
|
end
|
232
233
|
|
233
234
|
# Create or retrieve a circuit
|
@@ -243,10 +244,7 @@ class Faulty
|
|
243
244
|
# @return [Circuit] The new circuit or the existing circuit if it already exists
|
244
245
|
def circuit(name, **options, &block)
|
245
246
|
name = name.to_s
|
246
|
-
@
|
247
|
-
options = circuit_options.merge(options)
|
248
|
-
Circuit.new(name, **options, &block)
|
249
|
-
end
|
247
|
+
@registry.retrieve(name, options, &block)
|
250
248
|
end
|
251
249
|
|
252
250
|
# Get a list of all circuit names
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faulty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Howard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: redis
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -144,6 +158,7 @@ files:
|
|
144
158
|
- lib/faulty/cache/null.rb
|
145
159
|
- lib/faulty/cache/rails.rb
|
146
160
|
- lib/faulty/circuit.rb
|
161
|
+
- lib/faulty/circuit_registry.rb
|
147
162
|
- lib/faulty/error.rb
|
148
163
|
- lib/faulty/events.rb
|
149
164
|
- lib/faulty/events/callback_listener.rb
|