faulty 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eda80b5d7aba5948835addf9b8cf418aba0e8ba48453361678a15229ea4481a
4
- data.tar.gz: 53a27be99ae42c157be28df1e887033b86a53ccd232b05bc290bf511ac4e9d0c
3
+ metadata.gz: 8604d16866bb43e4b58ebd4d9efd9e3ae2a85555bd5e2d92c1f1ec8bfa24fb69
4
+ data.tar.gz: a766e4e122b41e4d58cab15fc0c249a254e940e523b54e8d906d00bcefafb5e2
5
5
  SHA512:
6
- metadata.gz: 766ab386706d842f4f02faa572d931b702b532071baa221f5c77c1330e3086cab1f2126f548c255254fe3d4f85ca9666698116b808c0b0e29c507ee5f6680c5c
7
- data.tar.gz: c5f04aa84ad450ae186c57236d80bb872c4977e60930a0f99e876bf497aa2473c2753d2307634b1cfe0dc332ce1d3c88382281ce2158c13cf140e432afaeb97e
6
+ metadata.gz: d2bf7c915310ab7eac608fc738f64df191555c7acfe1b6a28eb5a8e0519e42f2cf0512ba5bd7d13d62379783cdbefef69c9fa952d08ff78bf95d973afbd7f15e
7
+ data.tar.gz: 56963c2e80e34f2f56ac5a4efa405032f9eb72bd176ee0e7683a2f7dd0a4d26f06535773b9056c84c50d2d5349b2c2eb6d9dcc97fa98f3ef130245e3d65adb2b
data/CHANGELOG.md CHANGED
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
  [Unreleased]
10
10
  -------------------
11
11
 
12
+ [0.11.0] - 2023-04-26
13
+ ---------------------
14
+
15
+ ### Added
16
+
17
+ * Add storage support for redis gem v5 #63 justinhoward
18
+ * Add Redis 5 support for patch #67 justinhoward
19
+
12
20
  [0.10.0] - 2023-04-05
13
21
  ---------------------
14
22
 
@@ -300,7 +308,8 @@ of AutoWire.
300
308
 
301
309
  Initial public release
302
310
 
303
- [Unreleased]: https://github.com/ParentSquare/faulty/compare/v0.10.0...HEAD
311
+ [Unreleased]: https://github.com/ParentSquare/faulty/compare/v0.11.0...HEAD
312
+ [0.11.0]: https://github.com/ParentSquare/faulty/compare/v0.10.0...v0.11.0
304
313
  [0.10.0]: https://github.com/ParentSquare/faulty/compare/v0.9.0...v0.10.0
305
314
  [0.9.0]: https://github.com/ParentSquare/faulty/compare/v0.8.7...v0.9.0
306
315
  [0.8.7]: https://github.com/ParentSquare/faulty/compare/v0.8.6...v0.8.7
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![CI](https://github.com/ParentSquare/faulty/workflows/CI/badge.svg)](https://github.com/ParentSquare/faulty/actions?query=workflow%3ACI+branch%3Amaster)
5
5
  [![Code Quality](https://app.codacy.com/project/badge/Grade/16bb1df1569a4ddba893a866673dac2a)](https://www.codacy.com/gh/ParentSquare/faulty/dashboard?utm_source=github.com&utm_medium=referral&utm_content=ParentSquare/faulty&utm_campaign=Badge_Grade)
6
6
  [![Code Coverage](https://codecov.io/gh/ParentSquare/faulty/branch/master/graph/badge.svg?token=1NDT4FW1YJ)](https://codecov.io/gh/ParentSquare/faulty)
7
- [![Inline docs](http://inch-ci.org/github/ParentSquare/faulty.svg?branch=master)](http://inch-ci.org/github/ParentSquare/faulty)
7
+ [![Online docs](https://img.shields.io/badge/docs-✓-green.svg)](https://www.rubydoc.info/github/ParentSquare/faulty)
8
8
 
9
9
  Fault-tolerance tools for ruby based on [circuit-breakers][martin fowler].
10
10
 
@@ -985,11 +985,12 @@ Mysql2::Client.new(host: '127.0.0.1', faulty: { instance: 'mysql2' })
985
985
  protects a Redis client with an internal circuit. Pass a `:faulty` key along
986
986
  with your connection options to enable the circuit breaker.
987
987
 
988
- The Redis patch supports the Redis gem versions 3 and 4.
988
+ The Redis patch supports the Redis gem versions 3 and up
989
989
 
990
990
  ```ruby
991
991
  require 'faulty/patch/redis'
992
992
 
993
+ # For Redis <= 4, pass faulty into the top-level connection options
993
994
  redis = Redis.new(url: 'redis://localhost:6379', faulty: {
994
995
  # The name for the redis circuit
995
996
  name: 'redis'
@@ -1004,6 +1005,12 @@ redis = Redis.new(url: 'redis://localhost:6379', faulty: {
1004
1005
  # will raise its default errors
1005
1006
  patch_errors: true
1006
1007
  })
1008
+
1009
+ # Or for Redis 5+, pass faulty into the custom connection options
1010
+ redis = Redis.new(url: 'redis://localhost:6379', custom: { faulty: {
1011
+ # ...
1012
+ }})
1013
+
1007
1014
  redis.connect # raises Faulty::CircuitError if connection fails
1008
1015
 
1009
1016
  # If the faulty key is not given, no circuit is used
@@ -403,7 +403,7 @@ class Faulty
403
403
  cached_value
404
404
  end
405
405
 
406
- # Excecute a run
406
+ # Execute a run
407
407
  #
408
408
  # @param cached_value The cached value if one is available
409
409
  # @param cache_key [String, nil] The cache key if one is given
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Faulty
4
+ module Patch
5
+ module Redis
6
+ Patch.define_circuit_errors(self, ::RedisClient::ConnectionError)
7
+
8
+ class BusyError < ::RedisClient::CommandError
9
+ end
10
+
11
+ module Middleware
12
+ include Base
13
+
14
+ def initialize(client)
15
+ @faulty_circuit = Patch.circuit_from_hash(
16
+ 'redis',
17
+ client.config.custom[:faulty],
18
+ errors: [
19
+ ::RedisClient::ConnectionError,
20
+ BusyError
21
+ ],
22
+ patched_error_mapper: Faulty::Patch::Redis
23
+ )
24
+
25
+ super
26
+ end
27
+
28
+ def connect(redis_config)
29
+ faulty_run { super }
30
+ end
31
+
32
+ def call(commands, redis_config)
33
+ faulty_run { wrap_command { super } }
34
+ end
35
+
36
+ def call_pipelined(commands, redis_config)
37
+ faulty_run { wrap_command { super } }
38
+ end
39
+
40
+ private
41
+
42
+ def wrap_command
43
+ yield
44
+ rescue ::RedisClient::CommandError => e
45
+ raise BusyError, e.message if e.message.start_with?('BUSY')
46
+
47
+ raise
48
+ end
49
+ end
50
+
51
+ ::RedisClient.register(Middleware)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+
5
+ class Faulty
6
+ module Patch
7
+ module Redis
8
+ include Base
9
+
10
+ Patch.define_circuit_errors(self, ::Redis::BaseConnectionError)
11
+
12
+ class BusyError < ::Redis::CommandError
13
+ end
14
+
15
+ # Patches Redis to add the `:faulty` key
16
+ def initialize(options = {})
17
+ @faulty_circuit = Patch.circuit_from_hash(
18
+ 'redis',
19
+ options[:faulty],
20
+ errors: [
21
+ ::Redis::BaseConnectionError,
22
+ BusyError
23
+ ],
24
+ patched_error_mapper: Faulty::Patch::Redis
25
+ )
26
+
27
+ super
28
+ end
29
+
30
+ # The initial connection is protected by a circuit
31
+ def connect
32
+ faulty_run { super }
33
+ end
34
+
35
+ # Protect command calls
36
+ def call(command)
37
+ faulty_run { super }
38
+ end
39
+
40
+ # Protect command_loop calls
41
+ def call_loop(command, timeout = 0)
42
+ faulty_run { super }
43
+ end
44
+
45
+ # Protect pipelined commands
46
+ def call_pipelined(commands)
47
+ faulty_run { super }
48
+ end
49
+
50
+ # Inject specific error classes if client is patched
51
+ #
52
+ # This method does not raise errors, it returns them
53
+ # as exception objects, so we simply modify that error if necessary and
54
+ # return it.
55
+ #
56
+ # The call* methods above will then raise that error, so we are able to
57
+ # capture it with faulty_run.
58
+ def io(&block)
59
+ return super unless @faulty_circuit
60
+
61
+ reply = super
62
+ if reply.is_a?(::Redis::CommandError) && reply.message.start_with?('BUSY')
63
+ reply = BusyError.new(reply.message)
64
+ end
65
+
66
+ reply
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ class Redis
73
+ class Client
74
+ prepend(Faulty::Patch::Redis)
75
+ end
76
+ end
@@ -8,9 +8,12 @@ class Faulty
8
8
  #
9
9
  # This module is not required by default
10
10
  #
11
- # Pass a `:faulty` key into your MySQL connection options to enable
11
+ # Redis <= 4
12
+ # ---------------------
13
+ # Pass a `:faulty` key into your Redis connection options to enable
12
14
  # circuit protection. See {Patch.circuit_from_hash} for the available
13
- # options.
15
+ # options. On Redis 5+, the faulty key should be passed in the `:custom` hash
16
+ # instead of the top-level options. See example.
14
17
  #
15
18
  # By default, all circuit errors raised by this patch inherit from
16
19
  # `::Redis::BaseConnectionError`
@@ -18,7 +21,11 @@ class Faulty
18
21
  # @example
19
22
  # require 'faulty/patch/redis'
20
23
  #
24
+ # # Redis <= 4
21
25
  # redis = Redis.new(url: 'redis://localhost:6379', faulty: {})
26
+ # # Or for Redis 5+
27
+ # redis = Redis.new(url: 'redis://localhost:6379', custom: { faulty: {} })
28
+ #
22
29
  # redis.connect # raises Faulty::CircuitError if connection fails
23
30
  #
24
31
  # # If the faulty key is not given, no circuit is used
@@ -27,72 +34,12 @@ class Faulty
27
34
  #
28
35
  # @see Patch.circuit_from_hash
29
36
  module Redis
30
- include Base
31
-
32
- Patch.define_circuit_errors(self, ::Redis::BaseConnectionError)
33
-
34
- class BusyError < ::Redis::CommandError
35
- end
36
-
37
- # Patches Redis to add the `:faulty` key
38
- def initialize(options = {})
39
- @faulty_circuit = Patch.circuit_from_hash(
40
- 'redis',
41
- options[:faulty],
42
- errors: [
43
- ::Redis::BaseConnectionError,
44
- BusyError
45
- ],
46
- patched_error_mapper: Faulty::Patch::Redis
47
- )
48
-
49
- super
50
- end
51
-
52
- # The initial connection is protected by a circuit
53
- def connect
54
- faulty_run { super }
55
- end
56
-
57
- # Protect command calls
58
- def call(command)
59
- faulty_run { super }
60
- end
61
-
62
- # Protect command_loop calls
63
- def call_loop(command, timeout = 0)
64
- faulty_run { super }
65
- end
66
-
67
- # Protect pipelined commands
68
- def call_pipelined(commands)
69
- faulty_run { super }
70
- end
71
-
72
- # Inject specific error classes if client is patched
73
- #
74
- # This method does not raise errors, it returns them
75
- # as exception objects, so we simply modify that error if necessary and
76
- # return it.
77
- #
78
- # The call* methods above will then raise that error, so we are able to
79
- # capture it with faulty_run.
80
- def io(&block)
81
- return super unless @faulty_circuit
82
-
83
- reply = super
84
- if reply.is_a?(::Redis::CommandError) && reply.message.start_with?('BUSY')
85
- reply = BusyError.new(reply.message)
86
- end
87
-
88
- reply
89
- end
90
37
  end
91
38
  end
92
39
  end
93
40
 
94
- class Redis
95
- class Client
96
- prepend(Faulty::Patch::Redis)
97
- end
41
+ if Redis::VERSION.to_f < 5
42
+ require 'faulty/patch/redis/patch'
43
+ else
44
+ require 'faulty/patch/redis/middleware'
98
45
  end
@@ -25,7 +25,7 @@ class Faulty
25
25
  # circuit state. Default `faulty`.
26
26
  # @!attribute [r] key_separator
27
27
  # @return [String] A string used to separate the parts of the Redis keys
28
- # used to store circuit state. Defaulty `:`.
28
+ # used to store circuit state. Default `:`.
29
29
  # @!attribute [r] max_sample_size
30
30
  # @return [Integer] The number of cache run entries to keep in memory
31
31
  # for each circuit. Default `100`.
@@ -122,7 +122,7 @@ class Faulty
122
122
  def entry(circuit, time, success, status)
123
123
  key = entries_key(circuit.name)
124
124
  result = pipe do |r|
125
- r.sadd(list_key, circuit.name)
125
+ r.call([:sadd, list_key, circuit.name])
126
126
  r.expire(list_key, options.circuit_ttl + options.list_granularity) if options.circuit_ttl
127
127
  r.lpush(key, "#{time}#{ENTRY_SEPARATOR}#{success ? 1 : 0}")
128
128
  r.ltrim(key, 0, options.max_sample_size - 1)
@@ -425,11 +425,16 @@ class Faulty
425
425
  end
426
426
 
427
427
  def check_redis_options!
428
- ropts = redis { |r| r.instance_variable_get(:@client).options }
428
+ gte5 = ::Redis::VERSION.to_f >= 5
429
+ method = gte5 ? :config : :options
430
+ ropts = redis do |r|
431
+ r.instance_variable_get(:@client).public_send(method)
432
+ end
429
433
 
430
434
  bad_timeouts = {}
431
435
  %i[connect_timeout read_timeout write_timeout].each do |time_opt|
432
- bad_timeouts[time_opt] = ropts[time_opt] if ropts[time_opt] > 2
436
+ value = gte5 ? ropts.public_send(time_opt) : ropts[time_opt]
437
+ bad_timeouts[time_opt] = value if value > 2
433
438
  end
434
439
 
435
440
  unless bad_timeouts.empty?
@@ -440,10 +445,11 @@ class Faulty
440
445
  MSG
441
446
  end
442
447
 
443
- if ropts[:reconnect_attempts] > 1
448
+ gt1_retry = gte5 ? ropts.retry_connecting?(1, nil) : ropts[:reconnect_attempts] > 1
449
+ if gt1_retry
444
450
  warn <<~MSG
445
451
  Faulty recommends setting Redis reconnect_attempts to <= 1 to
446
- prevent cascading failures. Your setting is #{ropts[:reconnect_attempts]}
452
+ prevent cascading failures. Your setting is larger.
447
453
  MSG
448
454
  end
449
455
  end
@@ -3,6 +3,6 @@
3
3
  class Faulty
4
4
  # The current Faulty version
5
5
  def self.version
6
- Gem::Version.new('0.10.0')
6
+ Gem::Version.new('0.11.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.10.0
4
+ version: 0.11.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: 2023-04-05 00:00:00.000000000 Z
11
+ date: 2023-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -132,6 +132,8 @@ files:
132
132
  - lib/faulty/patch/elasticsearch.rb
133
133
  - lib/faulty/patch/mysql2.rb
134
134
  - lib/faulty/patch/redis.rb
135
+ - lib/faulty/patch/redis/middleware.rb
136
+ - lib/faulty/patch/redis/patch.rb
135
137
  - lib/faulty/result.rb
136
138
  - lib/faulty/status.rb
137
139
  - lib/faulty/storage.rb
@@ -150,7 +152,7 @@ licenses:
150
152
  metadata:
151
153
  rubygems_mfa_required: 'true'
152
154
  changelog_uri: https://github.com/ParentSquare/faulty/blob/master/CHANGELOG.md
153
- documentation_uri: https://www.rubydoc.info/gems/faulty/0.10.0
155
+ documentation_uri: https://www.rubydoc.info/gems/faulty/0.11.0
154
156
  post_install_message:
155
157
  rdoc_options: []
156
158
  require_paths: