faulty 0.10.0 → 0.11.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: 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: