faulty 0.2.0 → 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 +3 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +9 -0
- data/README.md +157 -9
- data/bin/check-version +5 -1
- data/lib/faulty.rb +11 -17
- data/lib/faulty/cache.rb +2 -0
- data/lib/faulty/cache/auto_wire.rb +65 -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 +9 -4
- data/lib/faulty/error.rb +14 -0
- data/lib/faulty/storage.rb +3 -0
- 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 +49 -54
- data/lib/faulty/storage/memory.rb +6 -2
- data/lib/faulty/storage/redis.rb +66 -4
- data/lib/faulty/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0712dd797dfe2922e4e52a792f48c5ac72c4b9766d218dfa597e7ad630f9fd0
|
4
|
+
data.tar.gz: fb4a787507006131f34a301fc164466fd8befb72051550ca0a63fc2d69ec949c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5381dd13f058045906330c67ff80aeefac55ae81077eb4552d5461d743344fa097e205b8ac3a39b385c52976e64315aec4e903c1a0c298a05452fab7788d1df2
|
7
|
+
data.tar.gz: af31cbf86cac54226189f8f63b1d8b0bcf82f9023085e98d07e4e9db65e402883c2219dbf255e21427c40b0c0a66ec71bd4dc9dfdd94fb9bc7b574ddf8426674
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## Release v0.3.0
|
2
|
+
|
3
|
+
* Add tools for backend fault-tolerance #10
|
4
|
+
* CircuitProxy for wrapping storage in an internal circuit
|
5
|
+
* FallbackChain storage backend for falling back to stable storage
|
6
|
+
* Timeout warnings for Redis backend
|
7
|
+
* AutoWire wrappers for automatically configuring storage and cache
|
8
|
+
* Better documentation for fault-tolerance
|
9
|
+
|
1
10
|
## Release v0.2.0
|
2
11
|
|
3
12
|
* Remove Scopes and replace them with Faulty instances #9
|
data/README.md
CHANGED
@@ -172,7 +172,8 @@ end.or_default([])
|
|
172
172
|
|
173
173
|
## How it Works
|
174
174
|
|
175
|
-
Faulty implements a version of circuit breakers inspired by
|
175
|
+
Faulty implements a version of circuit breakers inspired by "Release It!: Design
|
176
|
+
and Deploy Production-Ready Software" by [Michael T. Nygard][michael nygard] and
|
176
177
|
[Martin Fowler's post][martin fowler] on the subject. A few notable features of
|
177
178
|
Faulty's implementation are:
|
178
179
|
|
@@ -180,6 +181,7 @@ Faulty's implementation are:
|
|
180
181
|
- Integrated caching inspired by Netflix's [Hystrix][hystrix] with automatic
|
181
182
|
cache jitter and error fallback.
|
182
183
|
- Event-based monitoring
|
184
|
+
- Flexible fault-tolerant storage with optional fallbacks
|
183
185
|
|
184
186
|
Following the principals of the circuit-breaker pattern, the block given to
|
185
187
|
`run` or `try_run` will always be executed as long as it never raises an error.
|
@@ -215,11 +217,18 @@ Faulty.init do |config|
|
|
215
217
|
# The cache backend to use. By default, Faulty looks for a Rails cache. If
|
216
218
|
# that's not available, it uses an ActiveSupport::Cache::Memory instance.
|
217
219
|
# Otherwise, it uses a Faulty::Cache::Null and caching is disabled.
|
220
|
+
# Whatever backend is given here is automatically wrapped in
|
221
|
+
# Faulty::Cache::AutoWire. This adds fault-tolerance features, see the
|
222
|
+
# AutoWire API docs for more details.
|
218
223
|
config.cache = Faulty::Cache::Default.new
|
219
224
|
|
220
225
|
# The storage backend. By default, Faulty uses an in-memory store. For most
|
221
226
|
# production applications, you'll want a more robust backend. Faulty also
|
222
227
|
# provides Faulty::Storage::Redis for this.
|
228
|
+
# Whatever backend is given here is automatically wrapped in
|
229
|
+
# Faulty::Storage::AutoWire. This adds fault-tolerance features, see the
|
230
|
+
# AutoWire APi docs for more details. If an array of storage backends is
|
231
|
+
# given, each one will be tried in order until one succeeds.
|
223
232
|
config.storage = Faulty::Storage::Memory.new
|
224
233
|
|
225
234
|
# An array of event listeners. Each object in the array should implement
|
@@ -380,7 +389,12 @@ Faulty backends are fault-tolerant by default. Any `StandardError`s raised by
|
|
380
389
|
the storage or cache backends are captured and suppressed. Failure events for
|
381
390
|
these errors are sent to the notifier.
|
382
391
|
|
383
|
-
|
392
|
+
In case of a flaky storage or cache backend, Faulty also uses independent
|
393
|
+
in-memory circuits to track failures so that we don't keep calling a backend
|
394
|
+
that is failing. See the API docs for `Cache::AutoWire`, and `Storage::AutoWire`
|
395
|
+
for more details.
|
396
|
+
|
397
|
+
If the storage backend fails, circuits will default to closed. If the cache
|
384
398
|
backend fails, all cache queries will miss.
|
385
399
|
|
386
400
|
## Event Handling
|
@@ -456,10 +470,19 @@ end
|
|
456
470
|
|
457
471
|
## Configuring the Storage Backend
|
458
472
|
|
473
|
+
A storage backend is required to use Faulty. By default, it uses in-memory
|
474
|
+
storage, but Redis is also available, along with a number of wrappers used to
|
475
|
+
improve resiliency and fault-tolerance.
|
476
|
+
|
459
477
|
### Memory
|
460
478
|
|
461
|
-
The `Faulty::
|
462
|
-
|
479
|
+
The `Faulty::Storage::Memory` backend is the default storage backend. You may
|
480
|
+
prefer this implementation if you want to avoid the complexity and potential
|
481
|
+
failure-mode of cross-network circuit storage. The trade-off is that circuit
|
482
|
+
state is only contained within a single process and will not be saved across
|
483
|
+
application restarts. Locks will also be cleared on restart.
|
484
|
+
|
485
|
+
The default configuration:
|
463
486
|
|
464
487
|
```ruby
|
465
488
|
Faulty.init do |config|
|
@@ -472,15 +495,22 @@ end
|
|
472
495
|
|
473
496
|
### Redis
|
474
497
|
|
475
|
-
The `Faulty::
|
476
|
-
Redis.
|
498
|
+
The `Faulty::Storage::Redis` backend provides distributed circuit storage using
|
499
|
+
Redis. Although Faulty takes steps to reduce risk
|
500
|
+
(See [Fault Tolerance](#fault-tolerance)), using cross-network storage does
|
501
|
+
introduce some additional failure modes. To reduce this risk, be sure to set
|
502
|
+
conservative timeouts for your Redis connection. Setting high timeouts will
|
503
|
+
print warnings to stderr.
|
504
|
+
|
505
|
+
The default configuration:
|
477
506
|
|
478
507
|
```ruby
|
479
508
|
Faulty.init do |config|
|
480
509
|
config.storage = Faulty::Storage::Redis.new do |storage|
|
481
510
|
# The Redis client. Accepts either a Redis instance, or a ConnectionPool
|
482
|
-
# of Redis instances.
|
483
|
-
|
511
|
+
# of Redis instances. A low timeout is highly recommended to prevent
|
512
|
+
# cascading failures when evaluating circuits.
|
513
|
+
storage.client = ::Redis.new(timeout: 1)
|
484
514
|
|
485
515
|
# The prefix to prepend to all redis keys used by Faulty circuits
|
486
516
|
storage.key_prefix = 'faulty'
|
@@ -493,10 +523,126 @@ Faulty.init do |config|
|
|
493
523
|
|
494
524
|
# The maximum number of seconds that a circuit run will be stored
|
495
525
|
storage.sample_ttl = 1800
|
526
|
+
|
527
|
+
# The maximum number of seconds to store a circuit. Does not apply to
|
528
|
+
# locks, which are indefinite.
|
529
|
+
storage.circuit_ttl = 604_800 # 1 Week
|
530
|
+
|
531
|
+
# The number of seconds between circuit expirations. Changing this setting
|
532
|
+
# is not recommended. See API docs for more implementation details.
|
533
|
+
storage.list_granularity = 3600
|
534
|
+
|
535
|
+
# If true, disables warnings about recommended client settings like timeouts
|
536
|
+
storage.disable_warnings = false
|
496
537
|
end
|
497
538
|
end
|
498
539
|
```
|
499
540
|
|
541
|
+
### FallbackChain
|
542
|
+
|
543
|
+
The `Faulty::Storage::FallbackChain` backend is a wrapper for multiple
|
544
|
+
prioritized storage backends. If the first backend in the chain fails,
|
545
|
+
consecutive backends are tried until one succeeds. The recommended use-case for
|
546
|
+
this is to fall back on reliable storage if a networked storage backend fails.
|
547
|
+
|
548
|
+
For example, you may configure Redis as your primary storage backend, with an
|
549
|
+
in-memory storage backend as a fallback:
|
550
|
+
|
551
|
+
```ruby
|
552
|
+
Faulty.init do |config|
|
553
|
+
config.storage = Faulty::Storage::FallbackChain.new([
|
554
|
+
Faulty::Storage::Redis.new,
|
555
|
+
Faulty::Storage::Memory.new
|
556
|
+
])
|
557
|
+
end
|
558
|
+
```
|
559
|
+
|
560
|
+
Faulty instances will automatically use a fallback chain if an array is given to
|
561
|
+
the `storage` option, so this example is equivalent to the above:
|
562
|
+
|
563
|
+
```ruby
|
564
|
+
Faulty.init do |config|
|
565
|
+
config.storage = [
|
566
|
+
Faulty::Storage::Redis.new,
|
567
|
+
Faulty::Storage::Memory.new
|
568
|
+
]
|
569
|
+
end
|
570
|
+
```
|
571
|
+
|
572
|
+
If the fallback chain fails-over to backup storage, circuit states will not
|
573
|
+
carry over, so failover could be temporarily disruptive to your application.
|
574
|
+
However, any calls to `#lock` or `#unlock` will always be persisted to all
|
575
|
+
backends so that locks are maintained during failover.
|
576
|
+
|
577
|
+
### Storage::FaultTolerantProxy
|
578
|
+
|
579
|
+
This wrapper is applied to all non-fault-tolerant storage backends by default
|
580
|
+
(see the [API docs for `Faulty::Storage::AutoWire`](https://www.rubydoc.info/gems/faulty/Faulty/Storage/AutoWire)).
|
581
|
+
|
582
|
+
`Faulty::Storage::FaultTolerantProxy` is a wrapper that suppresses storage
|
583
|
+
errors and returns sensible defaults during failures. If a storage backend is
|
584
|
+
failing, all circuits will be treated as closed regardless of locks or previous
|
585
|
+
history.
|
586
|
+
|
587
|
+
If you wish your application to use a secondary storage backend instead of
|
588
|
+
failing closed, use `FallbackChain`.
|
589
|
+
|
590
|
+
### Storage::CircuitProxy
|
591
|
+
|
592
|
+
This wrapper is applied to all non-fault-tolerant storage backends by default
|
593
|
+
(see the [API docs for `Faulty::Storage::AutoWire`](https://www.rubydoc.info/gems/faulty/Faulty/Cache/AutoWire)).
|
594
|
+
|
595
|
+
`Faulty::Storage::CircuitProxy` is a wrapper that uses an independent in-memory
|
596
|
+
circuit to track failures to storage backends. If a storage backend fails
|
597
|
+
continuously, it will be temporarily disabled and raise `Faulty::CircuitError`s.
|
598
|
+
|
599
|
+
Typically this is used inside a `FaultTolerantProxy` or `FallbackChain` so that
|
600
|
+
these storage failures are handled gracefully.
|
601
|
+
|
602
|
+
## Configuring the Cache Backend
|
603
|
+
|
604
|
+
### Null
|
605
|
+
|
606
|
+
The `Faulty::Cache::Null` cache disables caching. It is the default if Rails and
|
607
|
+
ActiveSupport are not present.
|
608
|
+
|
609
|
+
### Rails
|
610
|
+
|
611
|
+
`Faulty::Cache::Rails` is the default cache if Rails or ActiveSupport are
|
612
|
+
present. If Rails is present, it uses `Rails.cache` as the backend. If
|
613
|
+
ActiveSupport is present, but Rails is not, it creates a new
|
614
|
+
`ActiveSupport::Cache::MemoryStore` by default. This backend can be used with
|
615
|
+
any `ActiveSupport::Cache`.
|
616
|
+
|
617
|
+
```ruby
|
618
|
+
Faulty.init do |config|
|
619
|
+
config.cache = Faulty::Cache::Rails.new(
|
620
|
+
ActiveSupport::Cache::RedisCacheStore.new
|
621
|
+
)
|
622
|
+
end
|
623
|
+
```
|
624
|
+
|
625
|
+
### Cache::FaultTolerantProxy
|
626
|
+
|
627
|
+
This wrapper is applied to all non-fault-tolerant cache backends by default
|
628
|
+
(see the API docs for `Faulty::Cache::AutoWire`).
|
629
|
+
|
630
|
+
`Faulty::Cache::FaultTolerantProxy` is a wrapper that suppresses cache errors
|
631
|
+
and acts like a null cache during failures. Reads always return `nil`, and
|
632
|
+
writes are no-ops.
|
633
|
+
|
634
|
+
### Cache::CircuitProxy
|
635
|
+
|
636
|
+
This wrapper is applied to all non-fault-tolerant circuit backends by default
|
637
|
+
(see the API docs for `Faulty::Circuit::AutoWire`).
|
638
|
+
|
639
|
+
`Faulty::Circuit::CircuitProxy` is a wrapper that uses an independent in-memory
|
640
|
+
circuit to track failures to circuit backends. If a circuit backend fails
|
641
|
+
continuously, it will be temporarily disabled and raise `Faulty::CircuitError`s.
|
642
|
+
|
643
|
+
Typically this is used inside a `FaultTolerantProxy` so that these cache
|
644
|
+
failures are handled gracefully.
|
645
|
+
|
500
646
|
## Listing Circuits
|
501
647
|
|
502
648
|
For monitoring or debugging, you may need to retrieve a list of all circuit
|
@@ -661,7 +807,7 @@ but there are and have been many other options:
|
|
661
807
|
- [semian](https://github.com/Shopify/semian): A resiliency toolkit that
|
662
808
|
includes circuit breakers. It uses adapters to auto-wire circuits, and it has
|
663
809
|
only host-local storage by design.
|
664
|
-
- [circuitbox](https://github.com/yammer/circuitbox): Similar in
|
810
|
+
- [circuitbox](https://github.com/yammer/circuitbox): Similar in design to
|
665
811
|
Faulty, but with a different API. It uses Moneta to abstract circuit storage
|
666
812
|
to allow any key-value store.
|
667
813
|
|
@@ -680,10 +826,12 @@ but there are and have been many other options:
|
|
680
826
|
|
681
827
|
- Simple API but configurable for advanced users
|
682
828
|
- Pluggable storage backends (circuitbox also has this)
|
829
|
+
- Protected storage access with fallback to safe storage
|
683
830
|
- Global, or object-oriented configuration with multiple instances
|
684
831
|
- Integrated caching support tailored for fault-tolerance
|
685
832
|
- Manually lock circuits open or closed
|
686
833
|
|
687
834
|
[api docs]: https://www.rubydoc.info/github/ParentSquare/faulty/master
|
835
|
+
[michael nygard]: https://www.michaelnygard.com/
|
688
836
|
[martin fowler]: https://www.martinfowler.com/bliki/CircuitBreaker.html
|
689
837
|
[hystrix]: https://github.com/Netflix/Hystrix/wiki/How-it-Works
|
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/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'
|
@@ -124,14 +125,17 @@ class Faulty
|
|
124
125
|
# Options for {Faulty}
|
125
126
|
#
|
126
127
|
# @!attribute [r] cache
|
128
|
+
# @see Cache::AutoWire
|
127
129
|
# @return [Cache::Interface] A cache backend if you want
|
128
130
|
# to use Faulty's cache support. Automatically wrapped in a
|
129
|
-
# {Cache::
|
131
|
+
# {Cache::AutoWire}. Default `Cache::AutoWire.new`.
|
130
132
|
# @!attribute [r] storage
|
131
|
-
# @
|
132
|
-
#
|
133
|
-
#
|
133
|
+
# @see Storage::AutoWire
|
134
|
+
# @return [Storage::Interface, Array<Storage::Interface>] The storage
|
135
|
+
# backend. Automatically wrapped in a {Storage::AutoWire}, so this can also
|
136
|
+
# be given an array of prioritized backends. Default `Storage::AutoWire.new`.
|
134
137
|
# @!attribute [r] listeners
|
138
|
+
# @see Events::ListenerInterface
|
135
139
|
# @return [Array] listeners Faulty event listeners
|
136
140
|
# @!attribute [r] notifier
|
137
141
|
# @return [Events::Notifier] A Faulty notifier. If given, listeners are
|
@@ -148,16 +152,8 @@ class Faulty
|
|
148
152
|
|
149
153
|
def finalize
|
150
154
|
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
|
155
|
+
self.storage = Storage::AutoWire.new(storage, notifier: notifier)
|
156
|
+
self.cache = Cache::AutoWire.new(cache, notifier: notifier)
|
161
157
|
end
|
162
158
|
|
163
159
|
def required
|
@@ -223,8 +219,6 @@ class Faulty
|
|
223
219
|
#
|
224
220
|
# @return [Hash] The circuit options
|
225
221
|
def circuit_options
|
226
|
-
options
|
227
|
-
options.delete(:listeners)
|
228
|
-
options
|
222
|
+
@options.to_h.select { |k, _v| %i[cache storage notifier].include?(k) }
|
229
223
|
end
|
230
224
|
end
|
data/lib/faulty/cache.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
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
|
+
extend Forwardable
|
10
|
+
|
11
|
+
# Options for {AutoWire}
|
12
|
+
Options = Struct.new(
|
13
|
+
:notifier
|
14
|
+
) do
|
15
|
+
include ImmutableOptions
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def required
|
20
|
+
%i[notifier]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Wrap a cache backend with sensible defaults
|
25
|
+
#
|
26
|
+
# If the cache is `nil`, create a new {Default}.
|
27
|
+
#
|
28
|
+
# If the backend is not fault tolerant, wrap it in {CircuitProxy} and
|
29
|
+
# {FaultTolerantProxy}.
|
30
|
+
#
|
31
|
+
# @param cache [Interface] A cache backend
|
32
|
+
# @param options [Hash] Attributes for {Options}
|
33
|
+
# @yield [Options] For setting options in a block
|
34
|
+
def initialize(cache, **options, &block)
|
35
|
+
@options = Options.new(options, &block)
|
36
|
+
@cache = if cache.nil?
|
37
|
+
Cache::Default.new
|
38
|
+
elsif cache.fault_tolerant?
|
39
|
+
cache
|
40
|
+
else
|
41
|
+
Cache::FaultTolerantProxy.new(
|
42
|
+
Cache::CircuitProxy.new(cache, notifier: @options.notifier),
|
43
|
+
notifier: @options.notifier
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!method read(key)
|
51
|
+
# (see Faulty::Cache::Interface#read)
|
52
|
+
#
|
53
|
+
# @!method write(key, value, expires_in: expires_in)
|
54
|
+
# (see Faulty::Cache::Interface#write)
|
55
|
+
def_delegators :@cache, :read, :write
|
56
|
+
|
57
|
+
# Auto-wired caches are always fault tolerant
|
58
|
+
#
|
59
|
+
# @return [true]
|
60
|
+
def fault_tolerant?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
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
|