faulty 0.4.0 → 0.5.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: 7e68341d29ccefb2a4fa4c265f3f2d5ec37371d37cb41d5a186b3f25d2130b46
4
- data.tar.gz: 56e659d66871738e8818c4874102de04ef97873cb0d1c444d58259c875b21378
3
+ metadata.gz: f8ce491fe09e0c6224c3ecc6da2825eeff601f15f7b5bbfbff065f898860fc48
4
+ data.tar.gz: caf56c1bfc86511d0acb6fbd3f7507d521bcd7b8ac74b47a85ebcbd9a4fdef1b
5
5
  SHA512:
6
- metadata.gz: 7db7b915923132bd1e5d234245cc5a3adb5988013f4baf8fadc59f89bacfde6828310954f0d6ea9da59ed18c97b9224e3d0f9e02ce2700ef9f6240e8b14d3706
7
- data.tar.gz: 578d4f0473ff58b1a9f6aeb3d2eab4be158de92943d6147e1521212068578ecd190094e17b02838919561d43b477b197344804478aa8288e70d0c30b26f85fd9
6
+ metadata.gz: 89eb97edae88432ea0e6e36a9c0b0f69d4d89075193ffd5a6eb0fe10e24c0983047289ccdb8e363619c4f83b1e80baec9decc34e4fdf029cfbef24189f244138
7
+ data.tar.gz: 4cabda8638d452bcac0a6375b284b4465d7eb34ed5e4b546a7247c09816eae4480620d69f9777144bd3d3aa8abcc58ca83243037e5d7aac0635e6d01d45ff289
data/.rubocop.yml CHANGED
@@ -8,6 +8,9 @@ AllCops:
8
8
  Layout/ArgumentAlignment:
9
9
  EnforcedStyle: with_fixed_indentation
10
10
 
11
+ Layout/CaseIndentation:
12
+ EnforcedStyle: end
13
+
11
14
  Layout/ParameterAlignment:
12
15
  EnforcedStyle: with_fixed_indentation
13
16
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## Release v0.5.0
2
+
3
+ * Allow creating a new Faulty instance in Faulty#register #24 justinhoward
4
+ * Add support for patches to core dependencies starting with redis #14 justinhoward
5
+ * Improve storage #entries performance by returning entries #23 justinhoward
6
+
7
+ ### Breaking Changes
8
+
9
+ * Faulty #[] no longer differentiates between symbols and strings when accessing
10
+ Faulty instances
11
+ * Faulty::Storage::Interface must now return a history array instead of a
12
+ circuit status object. Custom storage backends must be updated.
13
+
1
14
  ## Release v0.4.0
2
15
 
3
16
  * Switch from Travis CI to GitHub actions #11 justinhoward
data/README.md CHANGED
@@ -81,6 +81,8 @@ Also see "Release It!: Design and Deploy Production-Ready Software" by
81
81
  + [Circuit Options](#circuit-options)
82
82
  + [Listing Circuits](#listing-circuits)
83
83
  + [Locking Circuits](#locking-circuits)
84
+ * [Patches](#patches)
85
+ + [Patch::Redis](#patchredis)
84
86
  * [Event Handling](#event-handling)
85
87
  + [CallbackListener](#callbacklistener)
86
88
  + [Other Built-in Listeners](#other-built-in-listeners)
@@ -221,6 +223,10 @@ users = Faulty.circuit(:api).try_run do
221
223
  end.or_default([])
222
224
  ```
223
225
 
226
+ If you want to globally wrap your core dependencies, like your cache or
227
+ database, you may want to look at [Patches](#patches), which can automatically
228
+ wrap your connections in a Faulty circuit.
229
+
224
230
  See [Running a Circuit](#running-a-circuit) for more in-depth examples. Also,
225
231
  make sure you have proper [Event Handlers](#event-handling) setup so that you
226
232
  can monitor your circuits for failures.
@@ -580,6 +586,14 @@ principal to any other registered Faulty instance:
580
586
  Faulty[:api].circuit('api_circuit').run { 'ok' }
581
587
  ```
582
588
 
589
+ You can also create and register a Faulty instance in one step:
590
+
591
+ ```ruby
592
+ Faulty.register(:api) do |config|
593
+ # This accepts the same options as Faulty.init
594
+ end
595
+ ```
596
+
583
597
  #### Standalone Instances
584
598
 
585
599
  If you choose, you can use Faulty instances without registering them globally by
@@ -915,6 +929,59 @@ Locking or unlocking a circuit has no concurrency guarantees, so it's not
915
929
  recommended to lock or unlock circuits from production code. Instead, locks are
916
930
  intended as an emergency tool for troubleshooting and debugging.
917
931
 
932
+ ## Patches
933
+
934
+ For certain core dependencies like a cache or a database connection, it is
935
+ inconvenient to wrap every call in its own circuit. Faulty provides some patches
936
+ to wrap these calls in a circuit automatically. To use a patch, it first needs
937
+ to be loaded. Since patches modify third-party code, they are not automatically
938
+ required with the Faulty gem, so they need to be required individually.
939
+
940
+ ```ruby
941
+ require 'faulty'
942
+ require 'faulty/patch/redis'
943
+ ```
944
+
945
+ Or require them in your `Gemfile`
946
+
947
+ ```ruby
948
+ gem 'faulty', require: %w[faulty faulty/patch/redis]
949
+ ```
950
+
951
+ ### Patch::Redis
952
+
953
+ [`Faulty::Patch::Redis`](https://www.rubydoc.info/gems/faulty/Faulty/Patch/Redis)
954
+ protects a Redis client with an internal circuit. Pass a `:faulty` key along
955
+ with your connection options to enable the circuit breaker.
956
+
957
+ Keep in mind that when using this patch, you'll most likely want to use the
958
+ in-memory circuit storage adapter and not the Redis storage adapter. That way
959
+ if Redis fails, your circuit storage doesn't also fail.
960
+
961
+ ```ruby
962
+ require 'faulty/patch/redis'
963
+
964
+ redis = Redis.new(url: 'redis://localhost:6379', faulty: {
965
+ # The name for the redis circuit
966
+ name: 'redis'
967
+
968
+ # The faulty instance to use
969
+ # This can also be a registered faulty instance or a constant name. See API
970
+ # docs for more details
971
+ instance: Faulty.default
972
+
973
+ # By default, circuit errors will be subclasses of Redis::BaseError
974
+ # To disable this behavior, set patch_errors to false and Faulty
975
+ # will raise its default errors
976
+ patch_errors: true
977
+ })
978
+ redis.connect # raises Faulty::CircuitError if connection fails
979
+
980
+ # If the faulty key is not given, no circuit is used
981
+ redis = Redis.new(url: 'redis://localhost:6379')
982
+ redis.connect # not protected by a circuit
983
+ ```
984
+
918
985
  ## Event Handling
919
986
 
920
987
  Faulty uses an event-dispatching model to deliver notifications of internal
data/lib/faulty.rb CHANGED
@@ -9,6 +9,7 @@ require 'faulty/cache'
9
9
  require 'faulty/circuit'
10
10
  require 'faulty/error'
11
11
  require 'faulty/events'
12
+ require 'faulty/patch'
12
13
  require 'faulty/result'
13
14
  require 'faulty/status'
14
15
  require 'faulty/storage'
@@ -66,7 +67,7 @@ class Faulty
66
67
  def [](name)
67
68
  raise UninitializedError unless @instances
68
69
 
69
- @instances[name]
70
+ @instances[name.to_s]
70
71
  end
71
72
 
72
73
  # Register an instance to the global Faulty state
@@ -75,13 +76,22 @@ class Faulty
75
76
  # return value if you need to know whether the instance already existed.
76
77
  #
77
78
  # @param name [Symbol] The name of the instance to register
78
- # @param instance [Faulty] The instance to register
79
+ # @param instance [Faulty] The instance to register. If nil, a new instance
80
+ # will be created from the given options or block.
81
+ # @param config [Hash] Attributes for {Faulty::Options}
82
+ # @yield [Faulty::Options] For setting options in a block
79
83
  # @return [Faulty, nil] The previously-registered instance of that name if
80
84
  # it already existed, otherwise nil.
81
- def register(name, instance)
85
+ def register(name, instance = nil, **config, &block)
82
86
  raise UninitializedError unless @instances
83
87
 
84
- @instances.put_if_absent(name, instance)
88
+ if instance
89
+ raise ArgumentError, 'Do not give config options if an instance is given' if !config.empty? || block
90
+ else
91
+ instance = new(**config, &block)
92
+ end
93
+
94
+ @instances.put_if_absent(name.to_s, instance)
85
95
  end
86
96
 
87
97
  # Get the options for the default instance
@@ -50,6 +50,9 @@ class Faulty
50
50
  # @!attribute [r] cool_down
51
51
  # @return [Integer] The number of seconds the circuit will
52
52
  # stay open after it is tripped. Default 300.
53
+ # @!attribute [r] error_module
54
+ # @return [Module] Used by patches to set the namespace module for
55
+ # the faulty errors that will be raised. Default `Faulty`
53
56
  # @!attribute [r] evaluation_window
54
57
  # @return [Integer] The number of seconds of history that
55
58
  # will be evaluated to determine the failure rate for a circuit.
@@ -88,6 +91,7 @@ class Faulty
88
91
  :rate_threshold,
89
92
  :sample_threshold,
90
93
  :errors,
94
+ :error_module,
91
95
  :exclude,
92
96
  :cache,
93
97
  :notifier,
@@ -103,6 +107,7 @@ class Faulty
103
107
  cache_refreshes_after: 900,
104
108
  cool_down: 300,
105
109
  errors: [StandardError],
110
+ error_module: Faulty,
106
111
  exclude: [],
107
112
  evaluation_window: 60,
108
113
  rate_threshold: 0.5,
@@ -115,6 +120,7 @@ class Faulty
115
120
  cache
116
121
  cool_down
117
122
  errors
123
+ error_module
118
124
  exclude
119
125
  evaluation_window
120
126
  rate_threshold
@@ -213,9 +219,11 @@ class Faulty
213
219
  cached_value = cache_read(cache)
214
220
  # return cached unless cached.nil?
215
221
  return cached_value if !cached_value.nil? && !cache_should_refresh?(cache)
216
- return run_skipped(cached_value) unless status.can_run?
217
222
 
218
- run_exec(cached_value, cache, &block)
223
+ current_status = status
224
+ return run_skipped(cached_value) unless current_status.can_run?
225
+
226
+ run_exec(current_status, cached_value, cache, &block)
219
227
  end
220
228
 
221
229
  # Force the circuit to stay open until unlocked
@@ -282,7 +290,7 @@ class Faulty
282
290
  # @return The result from cache if available
283
291
  def run_skipped(cached_value)
284
292
  skipped!
285
- raise OpenCircuitError.new(nil, self) if cached_value.nil?
293
+ raise options.error_module::OpenCircuitError.new(nil, self) if cached_value.nil?
286
294
 
287
295
  cached_value
288
296
  end
@@ -292,26 +300,27 @@ class Faulty
292
300
  # @param cached_value The cached value if one is available
293
301
  # @param cache_key [String, nil] The cache key if one is given
294
302
  # @return The run result
295
- def run_exec(cached_value, cache_key)
303
+ def run_exec(status, cached_value, cache_key)
296
304
  result = yield
297
- success!
305
+ success!(status)
298
306
  cache_write(cache_key, result)
299
307
  result
300
308
  rescue *options.errors => e
301
309
  raise if options.exclude.any? { |ex| e.is_a?(ex) }
302
310
 
303
311
  if cached_value.nil?
304
- raise CircuitTrippedError.new(nil, self) if failure!(e)
312
+ raise options.error_module::CircuitTrippedError.new(nil, self) if failure!(status, e)
305
313
 
306
- raise CircuitFailureError.new(nil, self)
314
+ raise options.error_module::CircuitFailureError.new(nil, self)
307
315
  else
308
316
  cached_value
309
317
  end
310
318
  end
311
319
 
312
320
  # @return [Boolean] True if the circuit transitioned to closed
313
- def success!
314
- status = storage.entry(self, Faulty.current_time, true)
321
+ def success!(status)
322
+ entries = storage.entry(self, Faulty.current_time, true)
323
+ status = Status.from_entries(entries, **status.to_h)
315
324
  closed = false
316
325
  closed = close! if should_close?(status)
317
326
 
@@ -320,8 +329,9 @@ class Faulty
320
329
  end
321
330
 
322
331
  # @return [Boolean] True if the circuit transitioned to open
323
- def failure!(error)
324
- status = storage.entry(self, Faulty.current_time, false)
332
+ def failure!(status, error)
333
+ entries = storage.entry(self, Faulty.current_time, false)
334
+ status = Status.from_entries(entries, **status.to_h)
325
335
  options.notifier.notify(:circuit_failure, circuit: self, status: status, error: error)
326
336
 
327
337
  opened = if status.half_open?
data/lib/faulty/error.rb CHANGED
@@ -28,11 +28,13 @@ class Faulty
28
28
  end
29
29
  end
30
30
 
31
- # The base error for all errors raised during circuit runs
32
- #
33
- class CircuitError < FaultyError
31
+ # Included in faulty circuit errors to provide common features for
32
+ # native and patched errors
33
+ module CircuitErrorBase
34
34
  attr_reader :circuit
35
35
 
36
+ # @param message [String]
37
+ # @param circuit [Circuit] The circuit that raised the error
36
38
  def initialize(message, circuit)
37
39
  message ||= %(circuit error for "#{circuit.name}")
38
40
  @circuit = circuit
@@ -41,6 +43,12 @@ class Faulty
41
43
  end
42
44
  end
43
45
 
46
+ # The base error for all errors raised during circuit runs
47
+ #
48
+ class CircuitError < FaultyError
49
+ include CircuitErrorBase
50
+ end
51
+
44
52
  # Raised when running a circuit that is already open
45
53
  class OpenCircuitError < CircuitError; end
46
54
 
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faulty/patch/base'
4
+
5
+ class Faulty
6
+ # Automatic wrappers for common core dependencies like database connections
7
+ # or caches
8
+ module Patch
9
+ class << self
10
+ # Create a circuit from a configuration hash
11
+ #
12
+ # This is intended to be used in contexts where the user passes in
13
+ # something like a connection hash to a third-party library. For example
14
+ # the Redis patch hooks into the normal Redis connection options to add
15
+ # a `:faulty` key, which is a hash of faulty circuit options. This is
16
+ # slightly different from the normal Faulty circuit options because
17
+ # we also accept an `:instance` key which is a faulty instance.
18
+ #
19
+ # @example
20
+ # # We pass in a faulty instance along with some circuit options
21
+ # Patch.circuit_from_hash(
22
+ # :mysql,
23
+ # { host: 'localhost', faulty: {
24
+ # name: 'my_mysql', # A custom circuit name can be included
25
+ # instance: Faulty.new,
26
+ # sample_threshold: 5
27
+ # }
28
+ # }
29
+ # )
30
+ #
31
+ # @example
32
+ # # instance can be a registered faulty instance referenced by a string
33
+ # or symbol
34
+ # Faulty.register(:db_faulty, Faulty.new)
35
+ # Patch.circuit_from_hash(
36
+ # :mysql,
37
+ # { host: 'localhost', faulty: { instance: :db_faulty } }
38
+ # )
39
+ # @example
40
+ # # If instance is a hash with the key :constant, the value can be
41
+ # # a global constant name containing a Faulty instance
42
+ # DB_FAULTY = Faulty.new
43
+ # Patch.circuit_from_hash(
44
+ # :mysql,
45
+ # { host: 'localhost', faulty: { instance: { constant: 'DB_FAULTY' } } }
46
+ # )
47
+ #
48
+ # @example
49
+ # # Certain patches may want to enforce certain options like :errors
50
+ # # This can be done via hash or the usual block syntax
51
+ # Patch.circuit_from_hash(:mysql,
52
+ # { host: 'localhost', faulty: {} }
53
+ # errors: [Mysql2::Error]
54
+ # )
55
+ #
56
+ # Patch.circuit_from_hash(:mysql,
57
+ # { host: 'localhost', faulty: {} }
58
+ # ) do |conf|
59
+ # conf.errors = [Mysql2::Error]
60
+ # end
61
+ #
62
+ # @param default_name [String] The default name for the circuit
63
+ # @param hash [Hash] A hash of user-provided options. Supports any circuit
64
+ # option and these additional options
65
+ # @option hash [String] :name The circuit name. Defaults to `default_name`
66
+ # @option hash [Boolean] :patch_errors By default, circuit errors will be
67
+ # subclasses of `options[:patched_error_module]`. The user can disable
68
+ # this by setting this option to false.
69
+ # @option hash [Faulty, String, Symbol, Hash{ constant: String }] :instance
70
+ # A reference to a faulty instance. See examples.
71
+ # @param options [Hash] Additional override options. Supports any circuit
72
+ # option and these additional ones.
73
+ # @option options [Module] :patched_error_module The namespace module
74
+ # for patched errors
75
+ # @yield [Circuit::Options] For setting override options in a block
76
+ # @return [Circuit, nil] The circuit if one was created
77
+ def circuit_from_hash(default_name, hash, **options, &block)
78
+ return unless hash
79
+
80
+ hash = symbolize_keys(hash)
81
+ name = hash.delete(:name) || default_name
82
+ patch_errors = hash.delete(:patch_errors) != false
83
+ error_module = options.delete(:patched_error_module)
84
+ hash[:error_module] ||= error_module if error_module && patch_errors
85
+ faulty = resolve_instance(hash.delete(:instance))
86
+ faulty.circuit(name, **hash, **options, &block)
87
+ end
88
+
89
+ # Create a full set of {CircuitError}s with a given base error class
90
+ #
91
+ # For patches that need their errors to be subclasses of a common base.
92
+ #
93
+ # @param namespace [Module] The module to define the error classes in
94
+ # @param base [Class] The base class for the error classes
95
+ # @return [void]
96
+ def define_circuit_errors(namespace, base)
97
+ circuit_error = Class.new(base) { include CircuitErrorBase }
98
+ namespace.const_set('CircuitError', circuit_error)
99
+ namespace.const_set('OpenCircuitError', Class.new(circuit_error))
100
+ namespace.const_set('CircuitFailureError', Class.new(circuit_error))
101
+ namespace.const_set('CircuitTrippedError', Class.new(circuit_error))
102
+ end
103
+
104
+ private
105
+
106
+ # Resolves a constant from a constant name or returns a default
107
+ #
108
+ # - If value is a string or symbol, gets a registered Faulty instance with that name
109
+ # - If value is a Hash with a key `:constant`, resolves the value to a global constant
110
+ # - If value is nil, gets Faulty.default
111
+ # - Otherwise, return value directly
112
+ #
113
+ # @param value [String, Symbol, Faulty, nil] The object or constant name to resolve
114
+ # @return [Object] The resolved Faulty instance
115
+ def resolve_instance(value)
116
+ case value
117
+ when String, Symbol
118
+ result = Faulty[value]
119
+ raise NameError, "No Faulty instance for #{value}" unless result
120
+
121
+ result
122
+ when Hash
123
+ const_name = value[:constant]
124
+ raise ArgumentError 'Missing hash key :constant for Faulty instance' unless const_name
125
+
126
+ Kernel.const_get(const_name)
127
+ when nil
128
+ Faulty.default
129
+ else
130
+ value
131
+ end
132
+ end
133
+
134
+ # Some config files may not suport symbol keys, so we convert the hash
135
+ # to use symbols so that users can pass in strings
136
+ #
137
+ # We cannot use transform_keys since we support Ruby < 2.5
138
+ #
139
+ # @param hash [Hash] A hash to convert
140
+ # @return [Hash] The hash with keys as symbols
141
+ def symbolize_keys(hash)
142
+ result = {}
143
+ hash.each do |key, val|
144
+ result[key.to_sym] = if val.is_a?(Hash)
145
+ symbolize_keys(val)
146
+ else
147
+ val
148
+ end
149
+ end
150
+ result
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Faulty
4
+ module Patch
5
+ # Can be included in patch modules to provide common functionality
6
+ #
7
+ # The patch needs to set `@faulty_circuit`
8
+ #
9
+ # @example
10
+ # module ThingPatch
11
+ # include Faulty::Patch::Base
12
+ #
13
+ # def initialize(options = {})
14
+ # @faulty_circuit = Faulty::Patch.circuit_from_hash('thing', options[:faulty])
15
+ # end
16
+ #
17
+ # def do_something
18
+ # faulty_run { super }
19
+ # end
20
+ # end
21
+ #
22
+ # Thing.prepend(ThingPatch)
23
+ module Base
24
+ # Run a block wrapped by `@faulty_circuit`
25
+ #
26
+ # If `@faulty_circuit` is not set, the block will be run with no
27
+ # circuit.
28
+ #
29
+ # Nested calls to this method will only cause the circuit to be triggered
30
+ # once.
31
+ #
32
+ # @yield A block to run inside the circuit
33
+ # @return The block return value
34
+ def faulty_run
35
+ faulty_running_key = "faulty_running_#{object_id}"
36
+ return yield unless @faulty_circuit
37
+ return yield if Thread.current[faulty_running_key]
38
+
39
+ Thread.current[faulty_running_key] = true
40
+ @faulty_circuit.run { yield }
41
+ ensure
42
+ Thread.current[faulty_running_key] = false
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+
5
+ class Faulty
6
+ module Patch
7
+ # Patch Redis to run all network IO in a circuit
8
+ #
9
+ # This module is not required by default
10
+ #
11
+ # Pass a `:faulty` key into your redis connection options to enable
12
+ # circuit protection. This hash is a hash of circuit options for the
13
+ # internal circuit. The hash may also have a `:instance` key, which is the
14
+ # faulty instance to create the circuit from. `Faulty.default` will be
15
+ # used if no instance is given. The `:instance` key can also reference a
16
+ # registered Faulty instance or a global constantso that it can be set
17
+ # from config files. See {Patch.circuit_from_hash}.
18
+ #
19
+ # @example
20
+ # require 'faulty/patch/redis'
21
+ #
22
+ # redis = Redis.new(url: 'redis://localhost:6379', faulty: {})
23
+ # redis.connect # raises Faulty::CircuitError if connection fails
24
+ #
25
+ # # If the faulty key is not given, no circuit is used
26
+ # redis = Redis.new(url: 'redis://localhost:6379')
27
+ # redis.connect # not protected by a circuit
28
+ #
29
+ # @see Patch.circuit_from_hash
30
+ module Redis
31
+ include Base
32
+
33
+ Patch.define_circuit_errors(self, ::Redis::BaseConnectionError)
34
+
35
+ # Patches Redis to add the `:faulty` key
36
+ def initialize(options = {})
37
+ @faulty_circuit = Patch.circuit_from_hash(
38
+ 'redis',
39
+ options[:faulty],
40
+ errors: [::Redis::BaseConnectionError],
41
+ patched_error_module: Faulty::Patch::Redis
42
+ )
43
+
44
+ super
45
+ end
46
+
47
+ # The initial connection is protected by a circuit
48
+ def connect
49
+ faulty_run { super }
50
+ end
51
+
52
+ # Reads/writes to redis are protected
53
+ def io(&block)
54
+ faulty_run { super }
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ ::Redis::Client.prepend(Faulty::Patch::Redis)
@@ -14,7 +14,8 @@ class Faulty
14
14
  # @param circuit [Circuit] The circuit that ran
15
15
  # @param time [Integer] The unix timestamp for the run
16
16
  # @param success [Boolean] True if the run succeeded
17
- # @return [Status] The circuit status after the run is added
17
+ # @return [Array<Array>] An array of the new history tuples after adding
18
+ # the new entry, see {#history}
18
19
  def entry(circuit, time, success)
19
20
  raise NotImplementedError
20
21
  end
@@ -89,7 +89,7 @@ class Faulty
89
89
  runs.push([time, success])
90
90
  runs.shift if runs.size > options.max_sample_size
91
91
  end
92
- memory.status(circuit.options)
92
+ memory.runs.value
93
93
  end
94
94
 
95
95
  # Mark a circuit as open
@@ -95,15 +95,15 @@ class Faulty
95
95
  # @return (see Interface#entry)
96
96
  def entry(circuit, time, success)
97
97
  key = entries_key(circuit)
98
- pipe do |r|
98
+ result = pipe do |r|
99
99
  r.sadd(list_key, circuit.name)
100
100
  r.expire(list_key, options.circuit_ttl + options.list_granularity) if options.circuit_ttl
101
101
  r.lpush(key, "#{time}#{ENTRY_SEPARATOR}#{success ? 1 : 0}")
102
102
  r.ltrim(key, 0, options.max_sample_size - 1)
103
103
  r.expire(key, options.sample_ttl) if options.sample_ttl
104
+ r.lrange(key, 0, -1)
104
105
  end
105
-
106
- status(circuit)
106
+ map_entries(result.last)
107
107
  end
108
108
 
109
109
  # Mark a circuit as open
@@ -3,6 +3,6 @@
3
3
  class Faulty
4
4
  # The current Faulty version
5
5
  def self.version
6
- Gem::Version.new('0.4.0')
6
+ Gem::Version.new('0.5.0')
7
7
  end
8
8
  end
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.0
4
+ version: 0.5.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-02-19 00:00:00.000000000 Z
11
+ date: 2021-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -165,6 +165,9 @@ files:
165
165
  - lib/faulty/events/log_listener.rb
166
166
  - lib/faulty/events/notifier.rb
167
167
  - lib/faulty/immutable_options.rb
168
+ - lib/faulty/patch.rb
169
+ - lib/faulty/patch/base.rb
170
+ - lib/faulty/patch/redis.rb
168
171
  - lib/faulty/result.rb
169
172
  - lib/faulty/status.rb
170
173
  - lib/faulty/storage.rb
@@ -195,8 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  - !ruby/object:Gem::Version
196
199
  version: '0'
197
200
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.7.6
201
+ rubygems_version: 3.1.2
200
202
  signing_key:
201
203
  specification_version: 4
202
204
  summary: Fault-tolerance tools for ruby based on circuit-breakers