faulty 0.8.4 → 0.8.5
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/.github/workflows/ci.yml +3 -1
- data/.yardopts +1 -1
- data/CHANGELOG.md +7 -0
- data/lib/faulty/circuit.rb +59 -11
- data/lib/faulty/deprecation.rb +37 -0
- data/lib/faulty/error.rb +3 -0
- data/lib/faulty/patch/elasticsearch.rb +35 -3
- data/lib/faulty/patch/mysql2.rb +6 -2
- data/lib/faulty/patch/redis.rb +6 -2
- data/lib/faulty/patch.rb +6 -5
- data/lib/faulty/storage/auto_wire.rb +3 -3
- data/lib/faulty/storage/fallback_chain.rb +2 -2
- data/lib/faulty/storage/fault_tolerant_proxy.rb +3 -3
- data/lib/faulty/storage/interface.rb +5 -4
- data/lib/faulty/storage/memory.rb +3 -2
- data/lib/faulty/storage/null.rb +12 -6
- data/lib/faulty/storage/redis.rb +50 -31
- data/lib/faulty/version.rb +1 -1
- data/lib/faulty.rb +3 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54ef7d6bc1b23815e02563c80e4c17fef2735812a8c424882e8283996506edcc
|
4
|
+
data.tar.gz: c4c2d074b118f3077a3cddb862fddc5c3ace18c62a206e4523cfb830b7b6e35b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75fa840ff865e39e381894bdce59ab13f67ac001cbae57696360692c495b8d0ca2f643261a7a4ea083fdb1964d8e14ad0237815f90e9b436d17eff04a0abe0f5
|
7
|
+
data.tar.gz: dfcaede2d29e34bd466a21ad5037ddaeebfaa947bb44f09ef9c64b34ac0f207dd2a8e4d038067b47ce3ac76ccbba744721dc1fb4337854e7e9af0df739798169
|
data/.github/workflows/ci.yml
CHANGED
@@ -37,6 +37,9 @@ jobs:
|
|
37
37
|
bundler-cache: true
|
38
38
|
- run: bundle exec rubocop
|
39
39
|
if: matrix.ruby == '2.7'
|
40
|
+
- run: bin/yardoc --fail-on-warning
|
41
|
+
if: matrix.ruby == '2.7'
|
42
|
+
- run: bin/check-version
|
40
43
|
- name: start MySQL
|
41
44
|
run: sudo /etc/init.d/mysql start
|
42
45
|
- run: bundle exec rspec --format doc
|
@@ -48,7 +51,6 @@ jobs:
|
|
48
51
|
with:
|
49
52
|
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
50
53
|
coverage-reports: coverage/lcov/faulty.lcov
|
51
|
-
- run: bin/check-version
|
52
54
|
|
53
55
|
release:
|
54
56
|
needs: test
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## Release v0.8.5
|
2
|
+
|
3
|
+
* Fix yard warnings #49 justinhoward
|
4
|
+
* Fix crash in Redis storage backend if opened_at was missing #46 justinhoward
|
5
|
+
* Add granular errors for Elasticsearch patch #48 justinhoward
|
6
|
+
* Return status conditionally for Storage::Interface#entry #45 justinhoward
|
7
|
+
|
1
8
|
## Release v0.8.4
|
2
9
|
|
3
10
|
* Add Elasticsearch client patch #44 justinhoward
|
data/lib/faulty/circuit.rb
CHANGED
@@ -49,9 +49,13 @@ class Faulty
|
|
49
49
|
# @!attribute [r] cool_down
|
50
50
|
# @return [Integer] The number of seconds the circuit will
|
51
51
|
# stay open after it is tripped. Default 300.
|
52
|
-
# @!attribute [r]
|
53
|
-
# @return [Module] Used by patches to set the namespace module for
|
54
|
-
# the faulty errors that will be raised.
|
52
|
+
# @!attribute [r] error_mapper
|
53
|
+
# @return [Module, #call] Used by patches to set the namespace module for
|
54
|
+
# the faulty errors that will be raised. Should be a module or a callable.
|
55
|
+
# If given a module, the circuit assumes the module has error classes
|
56
|
+
# in that module. If given an object that responds to `#call` (a proc
|
57
|
+
# or lambda), the return value of the callable will be used. The callable
|
58
|
+
# is called with (`error_name`, `cause_error`, `circuit`). Default `Faulty`
|
55
59
|
# @!attribute [r] evaluation_window
|
56
60
|
# @return [Integer] The number of seconds of history that
|
57
61
|
# will be evaluated to determine the failure rate for a circuit.
|
@@ -93,6 +97,7 @@ class Faulty
|
|
93
97
|
:rate_threshold,
|
94
98
|
:sample_threshold,
|
95
99
|
:errors,
|
100
|
+
:error_mapper,
|
96
101
|
:error_module,
|
97
102
|
:exclude,
|
98
103
|
:cache,
|
@@ -120,7 +125,7 @@ class Faulty
|
|
120
125
|
cache_refreshes_after: 900,
|
121
126
|
cool_down: 300,
|
122
127
|
errors: [StandardError],
|
123
|
-
|
128
|
+
error_mapper: Faulty,
|
124
129
|
exclude: [],
|
125
130
|
evaluation_window: 60,
|
126
131
|
rate_threshold: 0.5,
|
@@ -133,7 +138,7 @@ class Faulty
|
|
133
138
|
cache
|
134
139
|
cool_down
|
135
140
|
errors
|
136
|
-
|
141
|
+
error_mapper
|
137
142
|
exclude
|
138
143
|
evaluation_window
|
139
144
|
rate_threshold
|
@@ -153,6 +158,17 @@ class Faulty
|
|
153
158
|
unless cache_refreshes_after.nil?
|
154
159
|
self.cache_refresh_jitter = 0.2 * cache_refreshes_after
|
155
160
|
end
|
161
|
+
|
162
|
+
deprecated_error_module
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def deprecated_error_module
|
168
|
+
return unless error_module
|
169
|
+
|
170
|
+
Deprecation.method(self.class, :error_module, note: 'See :error_mapper', sunset: '0.9.0')
|
171
|
+
self.error_mapper = error_module
|
156
172
|
end
|
157
173
|
end
|
158
174
|
|
@@ -379,7 +395,7 @@ class Faulty
|
|
379
395
|
# @return The result from cache if available
|
380
396
|
def run_skipped(cached_value)
|
381
397
|
skipped!
|
382
|
-
raise
|
398
|
+
raise map_error(:OpenCircuitError) if cached_value.nil?
|
383
399
|
|
384
400
|
cached_value
|
385
401
|
end
|
@@ -400,9 +416,9 @@ class Faulty
|
|
400
416
|
opened = failure!(status, e)
|
401
417
|
if cached_value.nil?
|
402
418
|
if opened
|
403
|
-
raise
|
419
|
+
raise map_error(:CircuitTrippedError, e)
|
404
420
|
else
|
405
|
-
raise
|
421
|
+
raise map_error(:CircuitFailureError, e)
|
406
422
|
end
|
407
423
|
else
|
408
424
|
cached_value
|
@@ -411,7 +427,11 @@ class Faulty
|
|
411
427
|
|
412
428
|
# @return [Boolean] True if the circuit transitioned to closed
|
413
429
|
def success!(status)
|
414
|
-
|
430
|
+
if deprecated_entry?
|
431
|
+
storage.entry(self, Faulty.current_time, true, nil)
|
432
|
+
else
|
433
|
+
storage.entry(self, Faulty.current_time, true)
|
434
|
+
end
|
415
435
|
closed = close! if status.half_open?
|
416
436
|
|
417
437
|
options.notifier.notify(:circuit_success, circuit: self)
|
@@ -420,8 +440,11 @@ class Faulty
|
|
420
440
|
|
421
441
|
# @return [Boolean] True if the circuit transitioned to open
|
422
442
|
def failure!(status, error)
|
423
|
-
|
424
|
-
|
443
|
+
status = if deprecated_entry?
|
444
|
+
storage.entry(self, Faulty.current_time, false, status)
|
445
|
+
else
|
446
|
+
deprecated_entry(status)
|
447
|
+
end
|
425
448
|
options.notifier.notify(:circuit_failure, circuit: self, status: status, error: error)
|
426
449
|
|
427
450
|
opened = if status.half_open?
|
@@ -435,6 +458,23 @@ class Faulty
|
|
435
458
|
opened
|
436
459
|
end
|
437
460
|
|
461
|
+
def deprecated_entry?
|
462
|
+
return @deprecated_entry unless @deprecated_entry.nil?
|
463
|
+
|
464
|
+
@deprecated_entry = storage.method(:entry).arity == 4
|
465
|
+
end
|
466
|
+
|
467
|
+
def deprecated_entry(status)
|
468
|
+
Faulty::Deprecation.deprecate(
|
469
|
+
'Returning entries array from entry',
|
470
|
+
note: 'see Storate::Interface#entry',
|
471
|
+
sunset: '0.9'
|
472
|
+
)
|
473
|
+
|
474
|
+
entries = storage.entry(self, Faulty.current_time, false)
|
475
|
+
Status.from_entries(entries, **status.to_h)
|
476
|
+
end
|
477
|
+
|
438
478
|
def skipped!
|
439
479
|
options.notifier.notify(:circuit_skipped, circuit: self)
|
440
480
|
end
|
@@ -531,5 +571,13 @@ class Faulty
|
|
531
571
|
|
532
572
|
@given_options.storage
|
533
573
|
end
|
574
|
+
|
575
|
+
def map_error(error_name, cause = nil)
|
576
|
+
if options.error_mapper.respond_to?(:call)
|
577
|
+
options.error_mapper.call(error_name, cause, self)
|
578
|
+
else
|
579
|
+
options.error_mapper.const_get(error_name).new(cause&.message, self)
|
580
|
+
end
|
581
|
+
end
|
534
582
|
end
|
535
583
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Faulty
|
4
|
+
# Support deprecating Faulty features
|
5
|
+
module Deprecation
|
6
|
+
class << self
|
7
|
+
# Call to raise errors instead of logging warnings for Faulty deprecations
|
8
|
+
def raise_errors!(enabled = true)
|
9
|
+
@raise_errors = (enabled == true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def silenced
|
13
|
+
@silence = true
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
@silence = false
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def method(klass, name, note: nil, sunset: nil)
|
21
|
+
deprecate("#{klass}##{name}", note: note, sunset: sunset)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @private
|
25
|
+
def deprecate(subject, note: nil, sunset: nil)
|
26
|
+
return if @silence
|
27
|
+
|
28
|
+
message = "#{subject} is deprecated"
|
29
|
+
message += " and will be removed in #{sunset}" if sunset
|
30
|
+
message += " (#{note})" if note
|
31
|
+
raise DeprecationError, message if @raise_errors
|
32
|
+
|
33
|
+
Kernel.warn("DEPRECATION: #{message}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/faulty/error.rb
CHANGED
@@ -36,7 +36,32 @@ class Faulty
|
|
36
36
|
module Elasticsearch
|
37
37
|
include Base
|
38
38
|
|
39
|
-
|
39
|
+
module Error; end
|
40
|
+
module SnifferTimeoutError; end
|
41
|
+
module ServerError; end
|
42
|
+
|
43
|
+
# We will freeze this after adding the dynamic error classes
|
44
|
+
MAPPED_ERRORS = { # rubocop:disable Style/MutableConstant
|
45
|
+
::Elasticsearch::Transport::Transport::Error => Error,
|
46
|
+
::Elasticsearch::Transport::Transport::SnifferTimeoutError => SnifferTimeoutError,
|
47
|
+
::Elasticsearch::Transport::Transport::ServerError => ServerError
|
48
|
+
}
|
49
|
+
|
50
|
+
module Errors
|
51
|
+
::Elasticsearch::Transport::Transport::ERRORS.each do |_code, klass|
|
52
|
+
MAPPED_ERRORS[klass] = const_set(klass.name.split('::').last, Module.new)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
MAPPED_ERRORS.freeze
|
57
|
+
MAPPED_ERRORS.each do |klass, mod|
|
58
|
+
Patch.define_circuit_errors(mod, klass)
|
59
|
+
end
|
60
|
+
|
61
|
+
ERROR_MAPPER = lambda do |error_name, cause, circuit|
|
62
|
+
MAPPED_ERRORS.fetch(cause&.class, Error).const_get(error_name).new(cause&.message, circuit)
|
63
|
+
end
|
64
|
+
private_constant :ERROR_MAPPER, :MAPPED_ERRORS
|
40
65
|
|
41
66
|
def initialize(arguments = {}, &block)
|
42
67
|
super
|
@@ -48,7 +73,8 @@ class Faulty
|
|
48
73
|
'elasticsearch',
|
49
74
|
arguments[:faulty],
|
50
75
|
errors: errors,
|
51
|
-
|
76
|
+
exclude: ::Elasticsearch::Transport::Transport::Errors::NotFound,
|
77
|
+
patched_error_mapper: ERROR_MAPPER
|
52
78
|
)
|
53
79
|
end
|
54
80
|
|
@@ -60,4 +86,10 @@ class Faulty
|
|
60
86
|
end
|
61
87
|
end
|
62
88
|
|
63
|
-
|
89
|
+
module Elasticsearch
|
90
|
+
module Transport
|
91
|
+
class Client
|
92
|
+
prepend(Faulty::Patch::Elasticsearch)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/faulty/patch/mysql2.rb
CHANGED
@@ -53,7 +53,7 @@ class Faulty
|
|
53
53
|
::Mysql2::Error::ConnectionError,
|
54
54
|
::Mysql2::Error::TimeoutError
|
55
55
|
],
|
56
|
-
|
56
|
+
patched_error_mapper: Faulty::Patch::Mysql2
|
57
57
|
)
|
58
58
|
|
59
59
|
super
|
@@ -81,4 +81,8 @@ class Faulty
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
|
84
|
+
module Mysql2
|
85
|
+
class Client
|
86
|
+
prepend(Faulty::Patch::Mysql2)
|
87
|
+
end
|
88
|
+
end
|
data/lib/faulty/patch/redis.rb
CHANGED
@@ -43,7 +43,7 @@ class Faulty
|
|
43
43
|
::Redis::BaseConnectionError,
|
44
44
|
BusyError
|
45
45
|
],
|
46
|
-
|
46
|
+
patched_error_mapper: Faulty::Patch::Redis
|
47
47
|
)
|
48
48
|
|
49
49
|
super
|
@@ -93,4 +93,8 @@ class Faulty
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
|
96
|
+
class Redis
|
97
|
+
class Client
|
98
|
+
prepend(Faulty::Patch::Redis)
|
99
|
+
end
|
100
|
+
end
|
data/lib/faulty/patch.rb
CHANGED
@@ -64,14 +64,15 @@ class Faulty
|
|
64
64
|
# option and these additional options
|
65
65
|
# @option hash [String] :name The circuit name. Defaults to `default_name`
|
66
66
|
# @option hash [Boolean] :patch_errors By default, circuit errors will be
|
67
|
-
# subclasses of `options[:
|
67
|
+
# subclasses of `options[:patched_error_mapper]`. The user can disable
|
68
68
|
# this by setting this option to false.
|
69
69
|
# @option hash [Faulty, String, Symbol, Hash{ constant: String }] :instance
|
70
70
|
# A reference to a faulty instance. See examples.
|
71
71
|
# @param options [Hash] Additional override options. Supports any circuit
|
72
72
|
# option and these additional ones.
|
73
|
-
# @option options [Module] :
|
74
|
-
# for patched errors
|
73
|
+
# @option options [Module] :patched_error_mapper The namespace module
|
74
|
+
# for patched errors or a mapping proc. See {Faulty::Circuit::Options}
|
75
|
+
# `:error_mapper`
|
75
76
|
# @yield [Circuit::Options] For setting override options in a block
|
76
77
|
# @return [Circuit, nil] The circuit if one was created
|
77
78
|
def circuit_from_hash(default_name, hash, **options, &block)
|
@@ -80,8 +81,8 @@ class Faulty
|
|
80
81
|
hash = symbolize_keys(hash)
|
81
82
|
name = hash.delete(:name) || default_name
|
82
83
|
patch_errors = hash.delete(:patch_errors) != false
|
83
|
-
|
84
|
-
hash[:
|
84
|
+
error_mapper = options.delete(:patched_error_mapper)
|
85
|
+
hash[:error_mapper] ||= error_mapper if error_mapper && patch_errors
|
85
86
|
faulty = resolve_instance(hash.delete(:instance))
|
86
87
|
faulty.circuit(name, **hash, **options, &block)
|
87
88
|
end
|
@@ -69,7 +69,7 @@ class Faulty
|
|
69
69
|
|
70
70
|
# Wrap an array of storage backends in a fault-tolerant FallbackChain
|
71
71
|
#
|
72
|
-
# @param [Array<Storage::Interface>] The array to wrap
|
72
|
+
# @param array [Array<Storage::Interface>] The array to wrap
|
73
73
|
# @param options [Options]
|
74
74
|
# @return [Storage::Interface] A fault-tolerant fallback chain
|
75
75
|
def wrap_array(array, options)
|
@@ -81,7 +81,7 @@ class Faulty
|
|
81
81
|
|
82
82
|
# Wrap one storage backend in fault-tolerant backends
|
83
83
|
#
|
84
|
-
# @param [Storage::Interface] The storage to wrap
|
84
|
+
# @param storage [Storage::Interface] The storage to wrap
|
85
85
|
# @param options [Options]
|
86
86
|
# @return [Storage::Interface] A fault-tolerant storage backend
|
87
87
|
def wrap_one(storage, options)
|
@@ -93,7 +93,7 @@ class Faulty
|
|
93
93
|
|
94
94
|
# Wrap storage in a CircuitProxy
|
95
95
|
#
|
96
|
-
# @param [Storage::Interface] The storage to wrap
|
96
|
+
# @param storage [Storage::Interface] The storage to wrap
|
97
97
|
# @param options [Options]
|
98
98
|
# @return [CircuitProxy]
|
99
99
|
def circuit_proxy(storage, options)
|
@@ -69,8 +69,8 @@ class Faulty
|
|
69
69
|
#
|
70
70
|
# @param (see Interface#entry)
|
71
71
|
# @return (see Interface#entry)
|
72
|
-
def entry(circuit, time, success)
|
73
|
-
send_chain(:entry, circuit, time, success) do |e|
|
72
|
+
def entry(circuit, time, success, status)
|
73
|
+
send_chain(:entry, circuit, time, success, status) do |e|
|
74
74
|
options.notifier.notify(:storage_failure, circuit: circuit, action: :entry, error: e)
|
75
75
|
end
|
76
76
|
end
|
@@ -112,11 +112,11 @@ class Faulty
|
|
112
112
|
# @see Interface#entry
|
113
113
|
# @param (see Interface#entry)
|
114
114
|
# @return (see Interface#entry)
|
115
|
-
def entry(circuit, time, success)
|
116
|
-
@storage.entry(circuit, time, success)
|
115
|
+
def entry(circuit, time, success, status)
|
116
|
+
@storage.entry(circuit, time, success, status)
|
117
117
|
rescue StandardError => e
|
118
118
|
options.notifier.notify(:storage_failure, circuit: circuit, action: :entry, error: e)
|
119
|
-
|
119
|
+
stub_status(circuit) if status
|
120
120
|
end
|
121
121
|
|
122
122
|
# Safely mark a circuit as open
|
@@ -21,7 +21,7 @@ class Faulty
|
|
21
21
|
# They should be returned exactly as given by {#set_options}
|
22
22
|
#
|
23
23
|
# @param circuit [Circuit] The circuit to set options for
|
24
|
-
# @param
|
24
|
+
# @param stored_options [Hash<Symbol, Object>] A hash of symbol option names to
|
25
25
|
# circuit options. These option values are guranteed to be primive
|
26
26
|
# values.
|
27
27
|
# @return [void]
|
@@ -37,9 +37,10 @@ class Faulty
|
|
37
37
|
# @param circuit [Circuit] The circuit that ran
|
38
38
|
# @param time [Integer] The unix timestamp for the run
|
39
39
|
# @param success [Boolean] True if the run succeeded
|
40
|
-
# @
|
41
|
-
# the new entry
|
42
|
-
|
40
|
+
# @param status [Status, nil] The previous status. If given, this method must
|
41
|
+
# return an updated status object from the new entry data.
|
42
|
+
# @return [Status, nil] If `status` is not nil, the updated status object.
|
43
|
+
def entry(circuit, time, success, status)
|
43
44
|
raise NotImplementedError
|
44
45
|
end
|
45
46
|
|
@@ -99,13 +99,14 @@ class Faulty
|
|
99
99
|
# @see Interface#entry
|
100
100
|
# @param (see Interface#entry)
|
101
101
|
# @return (see Interface#entry)
|
102
|
-
def entry(circuit, time, success)
|
102
|
+
def entry(circuit, time, success, status)
|
103
103
|
memory = fetch(circuit)
|
104
104
|
memory.runs.borrow do |runs|
|
105
105
|
runs.push([time, success])
|
106
106
|
runs.shift if runs.size > options.max_sample_size
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
|
+
Status.from_entries(memory.runs.value, **status.to_h) if status
|
109
110
|
end
|
110
111
|
|
111
112
|
# Mark a circuit as open
|
data/lib/faulty/storage/null.rb
CHANGED
@@ -24,8 +24,8 @@ class Faulty
|
|
24
24
|
|
25
25
|
# @param (see Interface#entry)
|
26
26
|
# @return (see Interface#entry)
|
27
|
-
def entry(
|
28
|
-
|
27
|
+
def entry(circuit, _time, _success, status)
|
28
|
+
stub_status(circuit) if status
|
29
29
|
end
|
30
30
|
|
31
31
|
# @param (see Interface#open)
|
@@ -64,10 +64,7 @@ class Faulty
|
|
64
64
|
# @param (see Interface#status)
|
65
65
|
# @return (see Interface#status)
|
66
66
|
def status(circuit)
|
67
|
-
|
68
|
-
options: circuit.options,
|
69
|
-
stub: true
|
70
|
-
)
|
67
|
+
stub_status(circuit)
|
71
68
|
end
|
72
69
|
|
73
70
|
# @param (see Interface#history)
|
@@ -89,6 +86,15 @@ class Faulty
|
|
89
86
|
def fault_tolerant?
|
90
87
|
true
|
91
88
|
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def stub_status(circuit)
|
93
|
+
Faulty::Status.new(
|
94
|
+
options: circuit.options,
|
95
|
+
stub: true
|
96
|
+
)
|
97
|
+
end
|
92
98
|
end
|
93
99
|
end
|
94
100
|
end
|
data/lib/faulty/storage/redis.rb
CHANGED
@@ -119,7 +119,7 @@ class Faulty
|
|
119
119
|
# @see Interface#entry
|
120
120
|
# @param (see Interface#entry)
|
121
121
|
# @return (see Interface#entry)
|
122
|
-
def entry(circuit, time, success)
|
122
|
+
def entry(circuit, time, success, status)
|
123
123
|
key = entries_key(circuit)
|
124
124
|
result = pipe do |r|
|
125
125
|
r.sadd(list_key, circuit.name)
|
@@ -127,9 +127,10 @@ class Faulty
|
|
127
127
|
r.lpush(key, "#{time}#{ENTRY_SEPARATOR}#{success ? 1 : 0}")
|
128
128
|
r.ltrim(key, 0, options.max_sample_size - 1)
|
129
129
|
r.expire(key, options.sample_ttl) if options.sample_ttl
|
130
|
-
r.lrange(key, 0, -1)
|
130
|
+
r.lrange(key, 0, -1) if status
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
|
+
Status.from_entries(map_entries(result.last), **status.to_h) if status
|
133
134
|
end
|
134
135
|
|
135
136
|
# Mark a circuit as open
|
@@ -138,11 +139,14 @@ class Faulty
|
|
138
139
|
# @param (see Interface#open)
|
139
140
|
# @return (see Interface#open)
|
140
141
|
def open(circuit, opened_at)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
142
|
+
key = state_key(circuit)
|
143
|
+
ex = options.circuit_ttl
|
144
|
+
result = watch_exec(key, ['closed', nil]) do |m|
|
145
|
+
m.set(key, 'open', ex: ex)
|
146
|
+
m.set(opened_at_key(circuit), opened_at, ex: ex)
|
145
147
|
end
|
148
|
+
|
149
|
+
result && result[0] == 'OK'
|
146
150
|
end
|
147
151
|
|
148
152
|
# Mark a circuit as reopened
|
@@ -151,9 +155,12 @@ class Faulty
|
|
151
155
|
# @param (see Interface#reopen)
|
152
156
|
# @return (see Interface#reopen)
|
153
157
|
def reopen(circuit, opened_at, previous_opened_at)
|
154
|
-
|
155
|
-
|
158
|
+
key = opened_at_key(circuit)
|
159
|
+
result = watch_exec(key, [previous_opened_at.to_s]) do |m|
|
160
|
+
m.set(key, opened_at, ex: options.circuit_ttl)
|
156
161
|
end
|
162
|
+
|
163
|
+
result && result[0] == 'OK'
|
157
164
|
end
|
158
165
|
|
159
166
|
# Mark a circuit as closed
|
@@ -162,11 +169,14 @@ class Faulty
|
|
162
169
|
# @param (see Interface#close)
|
163
170
|
# @return (see Interface#close)
|
164
171
|
def close(circuit)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
closed
|
172
|
+
key = state_key(circuit)
|
173
|
+
ex = options.circuit_ttl
|
174
|
+
result = watch_exec(key, ['open']) do |m|
|
175
|
+
m.set(key, 'closed', ex: ex)
|
176
|
+
m.del(entries_key(circuit))
|
169
177
|
end
|
178
|
+
|
179
|
+
result && result[0] == 'OK'
|
170
180
|
end
|
171
181
|
|
172
182
|
# Lock a circuit open or closed
|
@@ -220,11 +230,15 @@ class Faulty
|
|
220
230
|
futures[:entries] = r.lrange(entries_key(circuit), 0, -1)
|
221
231
|
end
|
222
232
|
|
233
|
+
state = futures[:state].value&.to_sym || :closed
|
234
|
+
opened_at = futures[:opened_at].value ? futures[:opened_at].value.to_i : nil
|
235
|
+
opened_at = Faulty.current_time - options.circuit_ttl if state == :open && opened_at.nil?
|
236
|
+
|
223
237
|
Faulty::Status.from_entries(
|
224
238
|
map_entries(futures[:entries].value),
|
225
|
-
state:
|
239
|
+
state: state,
|
226
240
|
lock: futures[:lock].value&.to_sym,
|
227
|
-
opened_at:
|
241
|
+
opened_at: opened_at,
|
228
242
|
options: circuit.options
|
229
243
|
)
|
230
244
|
end
|
@@ -329,23 +343,28 @@ class Faulty
|
|
329
343
|
(Faulty.current_time.to_f / options.list_granularity).floor
|
330
344
|
end
|
331
345
|
|
332
|
-
#
|
346
|
+
# Watch a Redis key and exec commands only if the key matches the expected
|
347
|
+
# value. Internally this uses Redis transactions with WATCH/MULTI/EXEC.
|
333
348
|
#
|
334
|
-
# @param
|
335
|
-
# @param
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
+
# @param key [String] The redis key to watch
|
350
|
+
# @param old [Array<String>] A list of previous values. The block will be
|
351
|
+
# run only if key is one of these values.
|
352
|
+
# @yield [Redis] A redis client. Commands executed using this client
|
353
|
+
# will be executed inside the MULTI context and will only be run if
|
354
|
+
# the watch succeeds and the comparison passes
|
355
|
+
# @return [Array] An array of Redis results from the commands executed
|
356
|
+
# inside the block
|
357
|
+
def watch_exec(key, old)
|
358
|
+
redis do |r|
|
359
|
+
r.watch(key) do
|
360
|
+
if old.include?(r.get(key))
|
361
|
+
r.multi do |m|
|
362
|
+
yield m
|
363
|
+
end
|
364
|
+
else
|
365
|
+
r.unwatch
|
366
|
+
nil
|
367
|
+
end
|
349
368
|
end
|
350
369
|
end
|
351
370
|
end
|
data/lib/faulty/version.rb
CHANGED
data/lib/faulty.rb
CHANGED
@@ -4,6 +4,7 @@ require 'securerandom'
|
|
4
4
|
require 'forwardable'
|
5
5
|
require 'concurrent'
|
6
6
|
|
7
|
+
require 'faulty/deprecation'
|
7
8
|
require 'faulty/immutable_options'
|
8
9
|
require 'faulty/cache'
|
9
10
|
require 'faulty/circuit'
|
@@ -143,14 +144,14 @@ class Faulty
|
|
143
144
|
@disabled = true
|
144
145
|
end
|
145
146
|
|
146
|
-
# Re-enable Faulty if disabled with {
|
147
|
+
# Re-enable Faulty if disabled with {.disable!}
|
147
148
|
#
|
148
149
|
# @return [void]
|
149
150
|
def enable!
|
150
151
|
@disabled = false
|
151
152
|
end
|
152
153
|
|
153
|
-
# Check whether Faulty was disabled with {
|
154
|
+
# Check whether Faulty was disabled with {.disable!}
|
154
155
|
#
|
155
156
|
# @return [Boolean] True if disabled
|
156
157
|
def disabled?
|
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.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Howard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -159,6 +159,7 @@ files:
|
|
159
159
|
- lib/faulty/cache/rails.rb
|
160
160
|
- lib/faulty/circuit.rb
|
161
161
|
- lib/faulty/circuit_registry.rb
|
162
|
+
- lib/faulty/deprecation.rb
|
162
163
|
- lib/faulty/error.rb
|
163
164
|
- lib/faulty/events.rb
|
164
165
|
- lib/faulty/events/callback_listener.rb
|