redis-cluster-client 0.16.0 → 0.16.2

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: 5fbdcc076f2a4980b7511a7e49a9b9d7ecc44379bb90c7a3ef38e2452da69d78
4
- data.tar.gz: b4e5c3afa0d587aef4fc52af7f5365c44a57c9cb7d80fe31bc237b3f04d78992
3
+ metadata.gz: f8d639d8939c9fdf304fa0f61886c1274f6e1c08bcd9e213dae3c7e0d844be02
4
+ data.tar.gz: 6f540ce88572df689c8a3e3971e3e96054b09b9fee7ef53c9aa518566ec3dd75
5
5
  SHA512:
6
- metadata.gz: 17b0a9668dfbccd0f407e86085937ca4604ff380d719fac340d2b4f9b30ea39322a701167482fdc696b22dd8f63f0836c9d94d35b1822049f5cb2f8f24ca609f
7
- data.tar.gz: 9a062aac27d5b8e5d7c907e034706b7671c4262bb692d3a9a00787ecc9bec4b12744429f3ea73e5deaedcf69c412df3591a5f6423233d5adff35cc300ac606d8
6
+ metadata.gz: 7d9c164a6590fb0ab3fa43328b484f8919895f17d3a6d5a9114c0c7b1a7867864be6d8e12ae1af45cdc81d39b9aa590e8c088de878ab964c18d9ae6f3c58682d
7
+ data.tar.gz: 01c0503f5f75d2441bdd2ea71537114c380db552e00ed8b130b0330626440f58a0d94bf94974dc044f4aebca9e024c931635dd8cbe5c96e225c80a7e10120840
@@ -30,13 +30,14 @@ class RedisClient
30
30
  regular_timeout = node.read_timeout
31
31
  node.read_timeout = slow_command_timeout > 0.0 ? slow_command_timeout : regular_timeout
32
32
  reply = node.call('command')
33
- node.read_timeout = regular_timeout
34
33
  commands = parse_command_reply(reply)
35
34
  cmd = ::RedisClient::Cluster::Command.new(commands)
36
35
  break
37
36
  rescue ::RedisClient::Error => e
38
37
  errors ||= []
39
38
  errors << e
39
+ ensure
40
+ node.read_timeout = regular_timeout
40
41
  end
41
42
 
42
43
  return cmd unless cmd.nil?
@@ -405,7 +405,10 @@ class RedisClient
405
405
 
406
406
  def parse_cluster_shards_reply(reply) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
407
407
  reply.each_with_object([]) do |shard, acc|
408
+ resp2 = shard.is_a?(Array)
409
+ shard = shard.each_slice(2).to_h if resp2
408
410
  nodes = shard.fetch('nodes')
411
+ nodes = nodes.map { |n| n.each_slice(2).to_h } if resp2
409
412
  primary_id = nodes.find { |n| n.fetch('role') == 'master' }.fetch('id')
410
413
 
411
414
  nodes.each do |node|
@@ -476,9 +479,11 @@ class RedisClient
476
479
  def with_reload_jitter
477
480
  return unless @next_reload_time.nil? || obtain_current_time >= @next_reload_time
478
481
 
479
- yield
480
-
481
- @next_reload_time = obtain_current_time + @random.rand(JITTER_WINDOW)
482
+ begin
483
+ yield
484
+ ensure
485
+ @next_reload_time = obtain_current_time + @random.rand(JITTER_WINDOW)
486
+ end
482
487
  end
483
488
 
484
489
  def with_reload_lock
@@ -18,12 +18,30 @@ class RedisClient
18
18
  end
19
19
 
20
20
  def split(node_key)
21
- pos = node_key&.rindex(DELIMITER, -1)
21
+ return [node_key, nil] if node_key.nil? || node_key.empty?
22
+
23
+ bracketed = split_bracketed(node_key)
24
+ return bracketed unless bracketed.nil?
25
+
26
+ pos = node_key.rindex(DELIMITER, -1)
22
27
  return [node_key, nil] if pos.nil?
23
28
 
24
29
  [node_key[0, pos], node_key[(pos + 1)..]]
25
30
  end
26
31
 
32
+ def split_bracketed(node_key)
33
+ return nil unless node_key.start_with?('[')
34
+
35
+ end_bracket = node_key.index(']')
36
+ return nil if end_bracket.nil?
37
+
38
+ host = node_key[1, end_bracket - 1]
39
+ remainder = node_key[(end_bracket + 1)..]
40
+ port = remainder.start_with?(DELIMITER) ? remainder[1..] : nil
41
+ [host, port]
42
+ end
43
+ private_class_method :split_bracketed
44
+
27
45
  def build_from_uri(uri)
28
46
  return '' if uri.nil?
29
47
 
@@ -54,7 +54,7 @@ class RedisClient
54
54
  rescue ::RedisClient::ConnectionError
55
55
  # Deduct the number of retries that happened _inside_ router#handle_redirection from our remaining
56
56
  # _external_ retries. Always deduct at least one in case handle_redirection raises without trying the block.
57
- retry_count -= [times_block_executed, 1].min
57
+ retry_count -= times_block_executed == 0 ? 1 : [times_block_executed, 1].min
58
58
  raise if retry_count < 0
59
59
 
60
60
  retry
@@ -61,7 +61,17 @@ class RedisClient
61
61
  end
62
62
 
63
63
  BUF_SIZE = Integer(ENV.fetch('REDIS_CLIENT_PUBSUB_BUF_SIZE', 1024))
64
- private_constant :BUF_SIZE
64
+ RECOVERY_BASE_INTERVAL = Float(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_INTERVAL_SEC', 1.0))
65
+ RECOVERY_MAX_INTERVAL = Float(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_MAX_INTERVAL_SEC', 30.0))
66
+ RECOVERY_MAX_ATTEMPTS = Integer(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_MAX_ATTEMPTS', 10))
67
+ SUBSCRIBE_COMMANDS = %w[subscribe psubscribe ssubscribe].freeze
68
+ UNSUBSCRIBE_TO_SUBSCRIBE = {
69
+ 'unsubscribe' => 'subscribe',
70
+ 'punsubscribe' => 'psubscribe',
71
+ 'sunsubscribe' => 'ssubscribe'
72
+ }.freeze
73
+ private_constant :BUF_SIZE, :RECOVERY_BASE_INTERVAL, :RECOVERY_MAX_INTERVAL, :RECOVERY_MAX_ATTEMPTS,
74
+ :SUBSCRIBE_COMMANDS, :UNSUBSCRIBE_TO_SUBSCRIBE
65
75
 
66
76
  def initialize(router, command_builder)
67
77
  @router = router
@@ -74,14 +84,14 @@ class RedisClient
74
84
  def call(*args, **kwargs)
75
85
  command = @command_builder.generate(args, kwargs)
76
86
  _call(command)
77
- @commands << command
87
+ remember(command)
78
88
  nil
79
89
  end
80
90
 
81
91
  def call_v(command)
82
92
  command = @command_builder.generate(command)
83
93
  _call(command)
84
- @commands << command
94
+ remember(command)
85
95
  nil
86
96
  end
87
97
 
@@ -118,6 +128,35 @@ class RedisClient
118
128
 
119
129
  private
120
130
 
131
+ def remember(command)
132
+ cmd = command.first.to_s.downcase
133
+ if SUBSCRIBE_COMMANDS.include?(cmd)
134
+ @commands << command
135
+ elsif (subscribe_cmd = UNSUBSCRIBE_TO_SUBSCRIBE[cmd])
136
+ forget_subscriptions(subscribe_cmd, command[1..])
137
+ else
138
+ @commands << command
139
+ end
140
+ end
141
+
142
+ def forget_subscriptions(subscribe_cmd, channels)
143
+ if channels.nil? || channels.empty?
144
+ @commands.reject! { |c| c.first.to_s.casecmp(subscribe_cmd).zero? }
145
+ else
146
+ @commands.reject! { |c| prune_entry(c, subscribe_cmd, channels) }
147
+ end
148
+ end
149
+
150
+ def prune_entry(entry, subscribe_cmd, channels)
151
+ return false unless entry.first.to_s.casecmp(subscribe_cmd).zero?
152
+
153
+ remaining = entry[1..] - channels
154
+ return true if remaining.empty?
155
+
156
+ entry.replace([entry.first, *remaining])
157
+ false
158
+ end
159
+
121
160
  def _call(command) # rubocop:disable Metrics/AbcSize
122
161
  if command.first.casecmp('subscribe').zero?
123
162
  call_to_single_state(command)
@@ -179,6 +218,7 @@ class RedisClient
179
218
  end
180
219
 
181
220
  def start_over
221
+ attempt = 0
182
222
  loop do
183
223
  @router.renew_cluster_state
184
224
  @state_dict.each_value(&:close)
@@ -186,9 +226,16 @@ class RedisClient
186
226
  @commands.each { |command| _call(command) }
187
227
  break
188
228
  rescue ::RedisClient::ConnectionError, ::RedisClient::Cluster::NodeMightBeDown
189
- sleep 1.0
229
+ attempt += 1
230
+ raise if attempt >= RECOVERY_MAX_ATTEMPTS
231
+
232
+ sleep recovery_interval(attempt)
190
233
  end
191
234
  end
235
+
236
+ def recovery_interval(attempt)
237
+ [RECOVERY_BASE_INTERVAL * (2**(attempt - 1)), RECOVERY_MAX_INTERVAL].min
238
+ end
192
239
  end
193
240
  end
194
241
  end
@@ -32,6 +32,12 @@ class RedisClient
32
32
 
33
33
  InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
34
34
 
35
+ SENSITIVE_INSPECT_KEYS = %i[username password].freeze
36
+ INSPECT_REDACTED_KEYS = %i[command_builder].freeze
37
+ INSPECT_PLACEHOLDER = '[FILTERED]'
38
+
39
+ private_constant :SENSITIVE_INSPECT_KEYS, :INSPECT_REDACTED_KEYS, :INSPECT_PLACEHOLDER
40
+
35
41
  attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
36
42
  :connect_with_original_config, :startup_nodes, :max_startup_sample, :id
37
43
 
@@ -65,7 +71,7 @@ class RedisClient
65
71
  end
66
72
 
67
73
  def inspect
68
- "#<#{self.class.name} #{startup_nodes.values.map { |v| v.reject { |k| k == :command_builder } }}>"
74
+ "#<#{self.class.name} #{startup_nodes.values.map { |v| redact_for_inspect(v) }}>"
69
75
  end
70
76
 
71
77
  def connect_timeout
@@ -117,6 +123,14 @@ class RedisClient
117
123
 
118
124
  private
119
125
 
126
+ def redact_for_inspect(node_config)
127
+ node_config.each_with_object({}) do |(key, value), redacted|
128
+ next if INSPECT_REDACTED_KEYS.include?(key)
129
+
130
+ redacted[key] = SENSITIVE_INSPECT_KEYS.include?(key) ? INSPECT_PLACEHOLDER : value
131
+ end
132
+ end
133
+
120
134
  def merge_concurrency_option(option)
121
135
  opts = {}
122
136
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.16.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga