faulty 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|