faulty 0.1.1 → 0.3.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/.rubocop.yml +9 -0
- data/.travis.yml +4 -2
- data/CHANGELOG.md +37 -1
- data/Gemfile +17 -0
- data/README.md +333 -55
- data/bin/check-version +5 -1
- data/bin/console +1 -1
- data/faulty.gemspec +3 -10
- data/lib/faulty.rb +149 -43
- data/lib/faulty/cache.rb +3 -1
- data/lib/faulty/cache/auto_wire.rb +65 -0
- data/lib/faulty/cache/circuit_proxy.rb +61 -0
- data/lib/faulty/cache/default.rb +10 -21
- data/lib/faulty/cache/fault_tolerant_proxy.rb +15 -4
- data/lib/faulty/cache/interface.rb +1 -1
- data/lib/faulty/cache/mock.rb +1 -1
- data/lib/faulty/cache/null.rb +1 -1
- data/lib/faulty/cache/rails.rb +9 -10
- data/lib/faulty/circuit.rb +10 -5
- data/lib/faulty/error.rb +18 -4
- data/lib/faulty/events.rb +3 -2
- data/lib/faulty/events/callback_listener.rb +1 -1
- data/lib/faulty/events/honeybadger_listener.rb +53 -0
- data/lib/faulty/events/listener_interface.rb +1 -1
- data/lib/faulty/events/log_listener.rb +1 -1
- data/lib/faulty/events/notifier.rb +11 -2
- data/lib/faulty/immutable_options.rb +1 -1
- data/lib/faulty/result.rb +2 -2
- data/lib/faulty/status.rb +1 -1
- data/lib/faulty/storage.rb +4 -1
- data/lib/faulty/storage/auto_wire.rb +122 -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 +55 -60
- data/lib/faulty/storage/interface.rb +1 -1
- data/lib/faulty/storage/memory.rb +8 -4
- data/lib/faulty/storage/redis.rb +75 -13
- data/lib/faulty/version.rb +2 -2
- metadata +13 -118
- data/lib/faulty/scope.rb +0 -117
data/lib/faulty/storage/redis.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
class Faulty
|
4
4
|
module Storage
|
5
|
+
# A storage backend for storing circuit state in Redis.
|
6
|
+
#
|
7
|
+
# When using this or any networked backend, be sure to evaluate the risk,
|
8
|
+
# and set conservative timeouts so that the circuit storage does not cause
|
9
|
+
# cascading failures in your application when evaluating circuits. Always
|
10
|
+
# wrap this backend with a {FaultTolerantProxy} to limit the effect of
|
11
|
+
# these types of events.
|
5
12
|
class Redis # rubocop:disable Metrics/ClassLength
|
6
13
|
# Separates the time/status for history entry strings
|
7
14
|
ENTRY_SEPARATOR = ':'
|
@@ -27,12 +34,17 @@ module Faulty
|
|
27
34
|
# circuit run history entry. Default `100`.
|
28
35
|
# @!attribute [r] circuit_ttl
|
29
36
|
# @return [Integer] The maximum number of seconds to keep a circuit.
|
30
|
-
# A value of `nil` disables circuit expiration.
|
37
|
+
# A value of `nil` disables circuit expiration. This does not apply to
|
38
|
+
# locks, which have an indefinite storage time.
|
31
39
|
# Default `604_800` (1 week).
|
32
40
|
# @!attribute [r] list_granularity
|
33
41
|
# @return [Integer] The number of seconds after which a new set is
|
34
42
|
# created to store circuit names. The old set is kept until
|
35
43
|
# circuit_ttl expires. Default `3600` (1 hour).
|
44
|
+
# @!attribute [r] disable_warnings
|
45
|
+
# @return [Boolean] By default, this class warns if the client options
|
46
|
+
# are outside the recommended values. Set to true to disable these
|
47
|
+
# warnings.
|
36
48
|
Options = Struct.new(
|
37
49
|
:client,
|
38
50
|
:key_prefix,
|
@@ -40,7 +52,8 @@ module Faulty
|
|
40
52
|
:max_sample_size,
|
41
53
|
:sample_ttl,
|
42
54
|
:circuit_ttl,
|
43
|
-
:list_granularity
|
55
|
+
:list_granularity,
|
56
|
+
:disable_warnings
|
44
57
|
) do
|
45
58
|
include ImmutableOptions
|
46
59
|
|
@@ -53,7 +66,8 @@ module Faulty
|
|
53
66
|
max_sample_size: 100,
|
54
67
|
sample_ttl: 1800,
|
55
68
|
circuit_ttl: 604_800,
|
56
|
-
list_granularity: 3600
|
69
|
+
list_granularity: 3600,
|
70
|
+
disable_warnings: false
|
57
71
|
}
|
58
72
|
end
|
59
73
|
|
@@ -62,7 +76,7 @@ module Faulty
|
|
62
76
|
end
|
63
77
|
|
64
78
|
def finalize
|
65
|
-
self.client = ::Redis.new unless client
|
79
|
+
self.client = ::Redis.new(timeout: 1) unless client
|
66
80
|
end
|
67
81
|
end
|
68
82
|
|
@@ -70,6 +84,8 @@ module Faulty
|
|
70
84
|
# @yield [Options] For setting options in a block
|
71
85
|
def initialize(**options, &block)
|
72
86
|
@options = Options.new(options, &block)
|
87
|
+
|
88
|
+
check_client_options!
|
73
89
|
end
|
74
90
|
|
75
91
|
# Add an entry to storage
|
@@ -97,7 +113,7 @@ module Faulty
|
|
97
113
|
# @return (see Interface#open)
|
98
114
|
def open(circuit, opened_at)
|
99
115
|
redis do |r|
|
100
|
-
opened = compare_and_set(r, state_key(circuit), ['closed', nil], 'open')
|
116
|
+
opened = compare_and_set(r, state_key(circuit), ['closed', nil], 'open', ex: options.circuit_ttl)
|
101
117
|
r.set(opened_at_key(circuit), opened_at, ex: options.circuit_ttl) if opened
|
102
118
|
opened
|
103
119
|
end
|
@@ -110,7 +126,7 @@ module Faulty
|
|
110
126
|
# @return (see Interface#reopen)
|
111
127
|
def reopen(circuit, opened_at, previous_opened_at)
|
112
128
|
redis do |r|
|
113
|
-
compare_and_set(r, opened_at_key(circuit), [previous_opened_at.to_s], opened_at)
|
129
|
+
compare_and_set(r, opened_at_key(circuit), [previous_opened_at.to_s], opened_at, ex: options.circuit_ttl)
|
114
130
|
end
|
115
131
|
end
|
116
132
|
|
@@ -121,7 +137,7 @@ module Faulty
|
|
121
137
|
# @return (see Interface#close)
|
122
138
|
def close(circuit)
|
123
139
|
redis do |r|
|
124
|
-
closed = compare_and_set(r, state_key(circuit), ['open'], 'closed')
|
140
|
+
closed = compare_and_set(r, state_key(circuit), ['open'], 'closed', ex: options.circuit_ttl)
|
125
141
|
r.del(entries_key(circuit)) if closed
|
126
142
|
closed
|
127
143
|
end
|
@@ -196,6 +212,9 @@ module Faulty
|
|
196
212
|
map_entries(entries).reverse
|
197
213
|
end
|
198
214
|
|
215
|
+
# List all unexpired circuits
|
216
|
+
#
|
217
|
+
# @return (see Interface#list)
|
199
218
|
def list
|
200
219
|
redis { |r| r.sunion(*all_list_keys) }
|
201
220
|
end
|
@@ -287,16 +306,16 @@ module Faulty
|
|
287
306
|
# @param new [String] The new value to set if the compare passes
|
288
307
|
# @return [Boolean] True if the value was set to `new`, false if the CAS
|
289
308
|
# failed
|
290
|
-
def compare_and_set(redis, key, old, new)
|
291
|
-
|
309
|
+
def compare_and_set(redis, key, old, new, ex:)
|
310
|
+
redis.watch(key) do
|
292
311
|
if old.include?(redis.get(key))
|
293
|
-
redis.multi { |m| m.set(key, new) }
|
312
|
+
result = redis.multi { |m| m.set(key, new, ex: ex) }
|
313
|
+
result && result[0] == 'OK'
|
294
314
|
else
|
295
315
|
redis.unwatch
|
316
|
+
false
|
296
317
|
end
|
297
318
|
end
|
298
|
-
|
299
|
-
result[0] == 'OK'
|
300
319
|
end
|
301
320
|
|
302
321
|
# Yield a Redis connection
|
@@ -330,6 +349,49 @@ module Faulty
|
|
330
349
|
[time.to_i, state == '1']
|
331
350
|
end
|
332
351
|
end
|
352
|
+
|
353
|
+
def check_client_options!
|
354
|
+
return if options.disable_warnings
|
355
|
+
|
356
|
+
check_redis_options!
|
357
|
+
check_pool_options!
|
358
|
+
rescue StandardError => e
|
359
|
+
warn "Faulty error while checking client options: #{e.message}"
|
360
|
+
end
|
361
|
+
|
362
|
+
def check_redis_options!
|
363
|
+
ropts = redis { |r| r.client.options }
|
364
|
+
|
365
|
+
bad_timeouts = {}
|
366
|
+
%i[connect_timeout read_timeout write_timeout].each do |time_opt|
|
367
|
+
bad_timeouts[time_opt] = ropts[time_opt] if ropts[time_opt] > 2
|
368
|
+
end
|
369
|
+
|
370
|
+
unless bad_timeouts.empty?
|
371
|
+
warn <<~MSG
|
372
|
+
Faulty recommends setting Redis timeouts <= 2 to prevent cascading
|
373
|
+
failures when evaluating circuits. Your options are:
|
374
|
+
#{bad_timeouts}
|
375
|
+
MSG
|
376
|
+
end
|
377
|
+
|
378
|
+
if ropts[:reconnect_attempts] > 1
|
379
|
+
warn <<~MSG
|
380
|
+
Faulty recommends setting Redis reconnect_attempts to <= 1 to
|
381
|
+
prevent cascading failures. Your setting is #{ropts[:reconnect_attempts]}
|
382
|
+
MSG
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def check_pool_options!
|
387
|
+
if options.client.is_a?(ConnectionPool)
|
388
|
+
timeout = options.client.instance_variable_get(:@timeout)
|
389
|
+
warn(<<~MSG) if timeout > 2
|
390
|
+
Faulty recommends setting ConnectionPool timeouts <= 2 to prevent
|
391
|
+
cascading failures when evaluating circuits. Your setting is #{timeout}
|
392
|
+
MSG
|
393
|
+
end
|
394
|
+
end
|
333
395
|
end
|
334
396
|
end
|
335
397
|
end
|
data/lib/faulty/version.rb
CHANGED
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.3.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: 2020-
|
11
|
+
date: 2020-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -24,54 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: activesupport
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '4.2'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '4.2'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.17'
|
48
|
-
- - "<"
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
version: '3'
|
51
|
-
type: :development
|
52
|
-
prerelease: false
|
53
|
-
version_requirements: !ruby/object:Gem::Requirement
|
54
|
-
requirements:
|
55
|
-
- - ">="
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: '1.17'
|
58
|
-
- - "<"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '3'
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
|
-
name: byebug
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '11.0'
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - "~>"
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: '11.0'
|
75
27
|
- !ruby/object:Gem::Dependency
|
76
28
|
name: connection_pool
|
77
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,33 +39,19 @@ dependencies:
|
|
87
39
|
- !ruby/object:Gem::Version
|
88
40
|
version: '2.0'
|
89
41
|
- !ruby/object:Gem::Dependency
|
90
|
-
name:
|
42
|
+
name: honeybadger
|
91
43
|
requirement: !ruby/object:Gem::Requirement
|
92
44
|
requirements:
|
93
|
-
- - "
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: '1.0'
|
96
|
-
type: :development
|
97
|
-
prerelease: false
|
98
|
-
version_requirements: !ruby/object:Gem::Requirement
|
99
|
-
requirements:
|
100
|
-
- - "~>"
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: '1.0'
|
103
|
-
- !ruby/object:Gem::Dependency
|
104
|
-
name: redcarpet
|
105
|
-
requirement: !ruby/object:Gem::Requirement
|
106
|
-
requirements:
|
107
|
-
- - "~>"
|
45
|
+
- - ">="
|
108
46
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
47
|
+
version: '2.0'
|
110
48
|
type: :development
|
111
49
|
prerelease: false
|
112
50
|
version_requirements: !ruby/object:Gem::Requirement
|
113
51
|
requirements:
|
114
|
-
- - "
|
52
|
+
- - ">="
|
115
53
|
- !ruby/object:Gem::Version
|
116
|
-
version: '
|
54
|
+
version: '2.0'
|
117
55
|
- !ruby/object:Gem::Dependency
|
118
56
|
name: redis
|
119
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,20 +80,6 @@ dependencies:
|
|
142
80
|
- - "~>"
|
143
81
|
- !ruby/object:Gem::Version
|
144
82
|
version: '3.8'
|
145
|
-
- !ruby/object:Gem::Dependency
|
146
|
-
name: rspec_junit_formatter
|
147
|
-
requirement: !ruby/object:Gem::Requirement
|
148
|
-
requirements:
|
149
|
-
- - "~>"
|
150
|
-
- !ruby/object:Gem::Version
|
151
|
-
version: '0.4'
|
152
|
-
type: :development
|
153
|
-
prerelease: false
|
154
|
-
version_requirements: !ruby/object:Gem::Requirement
|
155
|
-
requirements:
|
156
|
-
- - "~>"
|
157
|
-
- !ruby/object:Gem::Version
|
158
|
-
version: '0.4'
|
159
83
|
- !ruby/object:Gem::Dependency
|
160
84
|
name: rubocop
|
161
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,26 +108,6 @@ dependencies:
|
|
184
108
|
- - '='
|
185
109
|
- !ruby/object:Gem::Version
|
186
110
|
version: 1.38.1
|
187
|
-
- !ruby/object:Gem::Dependency
|
188
|
-
name: simplecov
|
189
|
-
requirement: !ruby/object:Gem::Requirement
|
190
|
-
requirements:
|
191
|
-
- - ">="
|
192
|
-
- !ruby/object:Gem::Version
|
193
|
-
version: 0.17.1
|
194
|
-
- - "<"
|
195
|
-
- !ruby/object:Gem::Version
|
196
|
-
version: '0.18'
|
197
|
-
type: :development
|
198
|
-
prerelease: false
|
199
|
-
version_requirements: !ruby/object:Gem::Requirement
|
200
|
-
requirements:
|
201
|
-
- - ">="
|
202
|
-
- !ruby/object:Gem::Version
|
203
|
-
version: 0.17.1
|
204
|
-
- - "<"
|
205
|
-
- !ruby/object:Gem::Version
|
206
|
-
version: '0.18'
|
207
111
|
- !ruby/object:Gem::Dependency
|
208
112
|
name: timecop
|
209
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -218,20 +122,6 @@ dependencies:
|
|
218
122
|
- - ">="
|
219
123
|
- !ruby/object:Gem::Version
|
220
124
|
version: '0.9'
|
221
|
-
- !ruby/object:Gem::Dependency
|
222
|
-
name: yard
|
223
|
-
requirement: !ruby/object:Gem::Requirement
|
224
|
-
requirements:
|
225
|
-
- - "~>"
|
226
|
-
- !ruby/object:Gem::Version
|
227
|
-
version: 0.9.25
|
228
|
-
type: :development
|
229
|
-
prerelease: false
|
230
|
-
version_requirements: !ruby/object:Gem::Requirement
|
231
|
-
requirements:
|
232
|
-
- - "~>"
|
233
|
-
- !ruby/object:Gem::Version
|
234
|
-
version: 0.9.25
|
235
125
|
description:
|
236
126
|
email:
|
237
127
|
- jmhoward0@gmail.com
|
@@ -258,6 +148,8 @@ files:
|
|
258
148
|
- faulty.gemspec
|
259
149
|
- lib/faulty.rb
|
260
150
|
- lib/faulty/cache.rb
|
151
|
+
- lib/faulty/cache/auto_wire.rb
|
152
|
+
- lib/faulty/cache/circuit_proxy.rb
|
261
153
|
- lib/faulty/cache/default.rb
|
262
154
|
- lib/faulty/cache/fault_tolerant_proxy.rb
|
263
155
|
- lib/faulty/cache/interface.rb
|
@@ -268,14 +160,17 @@ files:
|
|
268
160
|
- lib/faulty/error.rb
|
269
161
|
- lib/faulty/events.rb
|
270
162
|
- lib/faulty/events/callback_listener.rb
|
163
|
+
- lib/faulty/events/honeybadger_listener.rb
|
271
164
|
- lib/faulty/events/listener_interface.rb
|
272
165
|
- lib/faulty/events/log_listener.rb
|
273
166
|
- lib/faulty/events/notifier.rb
|
274
167
|
- lib/faulty/immutable_options.rb
|
275
168
|
- lib/faulty/result.rb
|
276
|
-
- lib/faulty/scope.rb
|
277
169
|
- lib/faulty/status.rb
|
278
170
|
- lib/faulty/storage.rb
|
171
|
+
- lib/faulty/storage/auto_wire.rb
|
172
|
+
- lib/faulty/storage/circuit_proxy.rb
|
173
|
+
- lib/faulty/storage/fallback_chain.rb
|
279
174
|
- lib/faulty/storage/fault_tolerant_proxy.rb
|
280
175
|
- lib/faulty/storage/interface.rb
|
281
176
|
- lib/faulty/storage/memory.rb
|
data/lib/faulty/scope.rb
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Faulty
|
4
|
-
# A {Scope} is a group of options and circuits
|
5
|
-
#
|
6
|
-
# For most use-cases the default scope should be used, however, it's possible
|
7
|
-
# to create any number of scopes for applications that require a more complex
|
8
|
-
# configuration or for testing.
|
9
|
-
#
|
10
|
-
# For the most part, scopes are independent, however for some cache and
|
11
|
-
# storage backends, you will need to ensure that the cache keys and circuit
|
12
|
-
# names don't overlap between scopes. For example, if using the Redis storage
|
13
|
-
# backend, you should specify different key prefixes for each scope.
|
14
|
-
class Scope
|
15
|
-
attr_reader :options
|
16
|
-
|
17
|
-
# Options for {Scope}
|
18
|
-
#
|
19
|
-
# @!attribute [r] cache
|
20
|
-
# @return [Cache::Interface] A cache backend if you want
|
21
|
-
# to use Faulty's cache support. Automatically wrapped in a
|
22
|
-
# {Cache::FaultTolerantProxy}. Default `Cache::Default.new`.
|
23
|
-
# @!attribute [r] storage
|
24
|
-
# @return [Storage::Interface] The storage backend.
|
25
|
-
# Automatically wrapped in a {Storage::FaultTolerantProxy}.
|
26
|
-
# Default `Storage::Memory.new`.
|
27
|
-
# @!attribute [r] listeners
|
28
|
-
# @return [Array] listeners Faulty event listeners
|
29
|
-
# @!attribute [r] notifier
|
30
|
-
# @return [Events::Notifier] A Faulty notifier. If given, listeners are
|
31
|
-
# ignored.
|
32
|
-
Options = Struct.new(
|
33
|
-
:cache,
|
34
|
-
:storage,
|
35
|
-
:listeners,
|
36
|
-
:notifier
|
37
|
-
) do
|
38
|
-
include ImmutableOptions
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def finalize
|
43
|
-
self.notifier ||= Events::Notifier.new(listeners || [])
|
44
|
-
|
45
|
-
self.storage ||= Storage::Memory.new
|
46
|
-
unless storage.fault_tolerant?
|
47
|
-
self.storage = Storage::FaultTolerantProxy.new(storage, notifier: notifier)
|
48
|
-
end
|
49
|
-
|
50
|
-
self.cache ||= Cache::Default.new
|
51
|
-
unless cache.fault_tolerant?
|
52
|
-
self.cache = Cache::FaultTolerantProxy.new(cache, notifier: notifier)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def required
|
57
|
-
%i[cache storage notifier]
|
58
|
-
end
|
59
|
-
|
60
|
-
def defaults
|
61
|
-
{
|
62
|
-
listeners: [Events::LogListener.new]
|
63
|
-
}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Create a new Faulty Scope
|
68
|
-
#
|
69
|
-
# Note, the process of creating a new scope is not thread safe,
|
70
|
-
# so make sure scopes are setup before spawning threads.
|
71
|
-
#
|
72
|
-
# @see Options
|
73
|
-
# @param options [Hash] Attributes for {Options}
|
74
|
-
# @yield [Options] For setting options in a block
|
75
|
-
def initialize(**options, &block)
|
76
|
-
@circuits = Concurrent::Map.new
|
77
|
-
@options = Options.new(options, &block)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Create or retrieve a circuit
|
81
|
-
#
|
82
|
-
# Within a scope, circuit instances have unique names, so if the given circuit
|
83
|
-
# name already exists, then the existing circuit will be returned, otherwise
|
84
|
-
# a new circuit will be created. If an existing circuit is returned, then
|
85
|
-
# the {options} param and block are ignored.
|
86
|
-
#
|
87
|
-
# @param name [String] The name of the circuit
|
88
|
-
# @param options [Hash] Attributes for {Circuit::Options}
|
89
|
-
# @yield [Circuit::Options] For setting options in a block
|
90
|
-
# @return [Circuit] The new circuit or the existing circuit if it already exists
|
91
|
-
def circuit(name, **options, &block)
|
92
|
-
name = name.to_s
|
93
|
-
options = options.merge(circuit_options)
|
94
|
-
@circuits.compute_if_absent(name) do
|
95
|
-
Circuit.new(name, **options, &block)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Get a list of all circuit names
|
100
|
-
#
|
101
|
-
# @return [Array<String>] The circuit names
|
102
|
-
def list_circuits
|
103
|
-
options.storage.list
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
# Get circuit options from the scope options
|
109
|
-
#
|
110
|
-
# @return [Hash] The circuit options
|
111
|
-
def circuit_options
|
112
|
-
options = @options.to_h
|
113
|
-
options.delete(:listeners)
|
114
|
-
options
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|