faulty 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d963d5c58861cc9e39c3222b5dc78a2a537c15fc10e0ed76d9d5b41a6704356b
4
- data.tar.gz: e31811fc1b5be36975982e966106ea7f323b4e334b87cfaca85667f550245584
3
+ metadata.gz: 815cc1b7ac571e9dc69546efd1b3892795a5ef284cc43448b8a6c1feadcf49c3
4
+ data.tar.gz: 575c2e0e7abb413f8866a0e159129a6f1ecc76149e8d8cb97c65c7ffd9913ba9
5
5
  SHA512:
6
- metadata.gz: 52aae6a3997fca7d38aebd124399747ee21a687114076e5d73c135d86ab147360e71a9f7a95ee4a5f548b6709089697e77fa472730c3fc18cb9c1d412bdac4ca
7
- data.tar.gz: 56b02cad8fe96373c916746bfc7c6f83665a74e7a3771d7b761244373b1d4b79aeb44d94fbe9401f82f8671b4e21c625a079b4845a42565dfbfbdfd37a07d2bf
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 calls to the same circuit will return a
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 created.
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
@@ -21,8 +21,6 @@ class Faulty
21
21
  ) do
22
22
  include ImmutableOptions
23
23
 
24
- private
25
-
26
24
  def required
27
25
  %i[notifier]
28
26
  end
@@ -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
 
@@ -22,8 +22,6 @@ class Faulty
22
22
  ) do
23
23
  include ImmutableOptions
24
24
 
25
- private
26
-
27
25
  def required
28
26
  %i[notifier]
29
27
  end
@@ -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
- private
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
- @options = Options.new(options, &block)
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
- options.storage
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).each { |key, value| self[key] = value }
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
- required.each do |key|
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
@@ -147,8 +147,6 @@ class Faulty
147
147
  failure_rate >= options.rate_threshold
148
148
  end
149
149
 
150
- private
151
-
152
150
  def finalize
153
151
  raise ArgumentError, "state must be a symbol in #{self.class}::STATES" unless STATES.include?(state)
154
152
  unless lock.nil? || LOCKS.include?(lock)
@@ -21,8 +21,6 @@ class Faulty
21
21
  ) do
22
22
  include ImmutableOptions
23
23
 
24
- private
25
-
26
24
  def required
27
25
  %i[notifier]
28
26
  end
@@ -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[entry open reopen close lock unlock reset status history list].each do |method|
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
@@ -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)
@@ -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')
@@ -3,6 +3,6 @@
3
3
  class Faulty
4
4
  # The current Faulty version
5
5
  def self.version
6
- Gem::Version.new('0.7.2')
6
+ Gem::Version.new('0.8.0')
7
7
  end
8
8
  end
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
- @circuits.compute_if_absent(name) do
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.7.2
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-02 00:00:00.000000000 Z
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