redis-cluster-client 0.7.11 → 0.8.1

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: 84e0a41582397ffdb79a5bad130f849ef3980dd00e602908c76006224e0663ed
4
- data.tar.gz: e987165bfcbbdda0ef5c122a88d36c21ebf7ade0ea6f8009203b7bb8a87a2736
3
+ metadata.gz: 53d1591b89859ddc92dc6ca349ff211d0a6ac14c68ad72e920cc9725d82587b4
4
+ data.tar.gz: fcfeceffddc9201f8b8c5d6999d3e7aa8581c9f24f0d823b029a5561ef82887d
5
5
  SHA512:
6
- metadata.gz: 2bd0e3de07bad00ea4ade79f397c75ee4e9b244655bd3f5bb6ecfec9a25ca0c3893fb9fa135eb16d01b0c1b7747375c4e5d37c3683befe47fcc34bbd0c34d619
7
- data.tar.gz: 5f84cd60815f57769ba3750f6f4e644ed4cdd933b5318ea4aa5ae725711c8ed87ea14e2ef252c92c66589645131e7a0a6381b2b8411fa56475768d2326cc69e6
6
+ metadata.gz: d51aefc48e64ef0cdbded8b5c8e4b005dcfd103d9fa4ecfa0dff1d9aa4fed5d6f6aefd4dca4ac53e89867b5276812bc6c7f8cf3cb41c1184bd9d8f19ea601471
7
+ data.tar.gz: 548da631726b4881f0a7712186f656733cb6941aef9ca3415498a6f048f59170fb74a59c30da68d655cee5e5773732d9d781c489f6cda2f0c92a341f1b28b892
@@ -4,7 +4,13 @@ class RedisClient
4
4
  class Cluster
5
5
  module ErrorIdentification
6
6
  def self.client_owns_error?(err, client)
7
- err.is_a?(TaggedError) && err.from?(client)
7
+ return true unless identifiable?(err)
8
+
9
+ err.from?(client)
10
+ end
11
+
12
+ def self.identifiable?(err)
13
+ err.is_a?(TaggedError)
8
14
  end
9
15
 
10
16
  module TaggedError
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'redis_client'
4
4
  require 'redis_client/config'
5
- require 'redis_client/cluster/error_identification'
6
5
  require 'redis_client/cluster/errors'
7
6
  require 'redis_client/cluster/node/primary_only'
8
7
  require 'redis_client/cluster/node/random_replica'
@@ -79,11 +78,9 @@ class RedisClient
79
78
  end
80
79
 
81
80
  class Config < ::RedisClient::Config
82
- def initialize(scale_read: false, middlewares: nil, **kwargs)
81
+ def initialize(scale_read: false, **kwargs)
83
82
  @scale_read = scale_read
84
- middlewares ||= []
85
- middlewares.unshift ErrorIdentification::Middleware
86
- super(middlewares: middlewares, **kwargs)
83
+ super(**kwargs)
87
84
  end
88
85
 
89
86
  private
@@ -217,10 +214,6 @@ class RedisClient
217
214
  end
218
215
  end
219
216
 
220
- def owns_error?(err)
221
- any? { |c| ErrorIdentification.client_owns_error?(err, c) }
222
- end
223
-
224
217
  private
225
218
 
226
219
  def make_topology_class(with_replica, replica_affinity)
@@ -8,26 +8,56 @@ class RedisClient
8
8
  class OptimisticLocking
9
9
  def initialize(router)
10
10
  @router = router
11
+ @asking = false
11
12
  end
12
13
 
13
14
  def watch(keys)
14
15
  slot = find_slot(keys)
15
16
  raise ::RedisClient::Cluster::Transaction::ConsistencyError, "unsafe watch: #{keys.join(' ')}" if slot.nil?
16
17
 
18
+ # We have not yet selected a node for this transaction, initially, which means we can handle
19
+ # redirections freely initially (i.e. for the first WATCH call)
17
20
  node = @router.find_primary_node_by_slot(slot)
18
- @router.handle_redirection(node, retry_count: 1) do |nd|
21
+ handle_redirection(node, retry_count: 1) do |nd|
19
22
  nd.with do |c|
20
- c.call('WATCH', *keys)
21
- yield(c, slot)
22
- rescue StandardError
23
- c.call('UNWATCH')
24
- raise
23
+ c.ensure_connected_cluster_scoped(retryable: false) do
24
+ c.call('ASKING') if @asking
25
+ c.call('WATCH', *keys)
26
+ begin
27
+ yield(c, slot, @asking)
28
+ rescue ::RedisClient::ConnectionError
29
+ # No need to unwatch on a connection error.
30
+ raise
31
+ rescue StandardError
32
+ c.call('UNWATCH')
33
+ raise
34
+ end
35
+ end
25
36
  end
26
37
  end
27
38
  end
28
39
 
29
40
  private
30
41
 
42
+ def handle_redirection(node, retry_count: 1, &blk)
43
+ @router.handle_redirection(node, retry_count: retry_count) do |nd|
44
+ handle_asking_once(nd, &blk)
45
+ end
46
+ end
47
+
48
+ def handle_asking_once(node)
49
+ yield node
50
+ rescue ::RedisClient::CommandError => e
51
+ raise unless ErrorIdentification.client_owns_error?(e, node)
52
+ raise unless e.message.start_with?('ASK')
53
+
54
+ node = @router.assign_asking_node(e.message)
55
+ @asking = true
56
+ yield node
57
+ ensure
58
+ @asking = false
59
+ end
60
+
31
61
  def find_slot(keys)
32
62
  return if keys.empty?
33
63
  return if keys.any? { |k| k.nil? || k.empty? }
@@ -12,7 +12,7 @@ class RedisClient
12
12
  class Extended < ::RedisClient::Pipeline
13
13
  attr_reader :outer_indices
14
14
 
15
- def initialize(command_builder)
15
+ def initialize(...)
16
16
  super
17
17
  @outer_indices = nil
18
18
  end
@@ -50,14 +50,14 @@ class RedisClient
50
50
  end
51
51
 
52
52
  ::RedisClient::ConnectionMixin.module_eval do
53
- def call_pipelined_aware_of_redirection(commands, timeouts) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
53
+ def call_pipelined_aware_of_redirection(commands, timeouts, exception:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
54
54
  size = commands.size
55
55
  results = Array.new(commands.size)
56
56
  @pending_reads += size
57
57
  write_multi(commands)
58
58
 
59
59
  redirection_indices = nil
60
- exception = nil
60
+ first_exception = nil
61
61
  size.times do |index|
62
62
  timeout = timeouts && timeouts[index]
63
63
  result = read(timeout)
@@ -67,14 +67,14 @@ class RedisClient
67
67
  if result.is_a?(::RedisClient::CommandError) && result.message.start_with?('MOVED', 'ASK')
68
68
  redirection_indices ||= []
69
69
  redirection_indices << index
70
- else
71
- exception ||= result
70
+ elsif exception
71
+ first_exception ||= result
72
72
  end
73
73
  end
74
74
  results[index] = result
75
75
  end
76
76
 
77
- raise exception if exception
77
+ raise first_exception if exception && first_exception
78
78
  return results if redirection_indices.nil?
79
79
 
80
80
  err = ::RedisClient::Cluster::Pipeline::RedirectionNeeded.new
@@ -98,10 +98,11 @@ class RedisClient
98
98
  attr_accessor :replies, :indices
99
99
  end
100
100
 
101
- def initialize(router, command_builder, concurrent_worker, seed: Random.new_seed)
101
+ def initialize(router, command_builder, concurrent_worker, exception:, seed: Random.new_seed)
102
102
  @router = router
103
103
  @command_builder = command_builder
104
104
  @concurrent_worker = concurrent_worker
105
+ @exception = exception
105
106
  @seed = seed
106
107
  @pipelines = nil
107
108
  @size = 0
@@ -212,7 +213,7 @@ class RedisClient
212
213
  results = client.ensure_connected_cluster_scoped(retryable: pipeline._retryable?) do |connection|
213
214
  commands = pipeline._commands
214
215
  client.middlewares.call_pipelined(commands, client.config) do
215
- connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts)
216
+ connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts, exception: @exception)
216
217
  end
217
218
  end
218
219
 
@@ -224,15 +225,21 @@ class RedisClient
224
225
 
225
226
  if err.message.start_with?('MOVED')
226
227
  node = @router.assign_redirection_node(err.message)
227
- redirect_command(node, pipeline, inner_index)
228
+ try_redirection(node, pipeline, inner_index)
228
229
  elsif err.message.start_with?('ASK')
229
230
  node = @router.assign_asking_node(err.message)
230
- try_asking(node) ? redirect_command(node, pipeline, inner_index) : err
231
+ try_asking(node) ? try_redirection(node, pipeline, inner_index) : err
231
232
  else
232
233
  err
233
234
  end
234
235
  end
235
236
 
237
+ def try_redirection(node, pipeline, inner_index)
238
+ redirect_command(node, pipeline, inner_index)
239
+ rescue StandardError => e
240
+ @exception ? raise : e
241
+ end
242
+
236
243
  def redirect_command(node, pipeline, inner_index)
237
244
  method = pipeline.get_callee_method(inner_index)
238
245
  command = pipeline.get_command(inner_index)
@@ -10,6 +10,7 @@ require 'redis_client/cluster/node_key'
10
10
  require 'redis_client/cluster/normalized_cmd_name'
11
11
  require 'redis_client/cluster/transaction'
12
12
  require 'redis_client/cluster/optimistic_locking'
13
+ require 'redis_client/cluster/error_identification'
13
14
 
14
15
  class RedisClient
15
16
  class Cluster
@@ -68,7 +69,9 @@ class RedisClient
68
69
  raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
69
70
 
70
71
  update_cluster_info! if e.errors.values.any? do |err|
71
- @node.owns_error?(err) && err.message.start_with?('CLUSTERDOWN Hash slot not served')
72
+ next false if ::RedisClient::Cluster::ErrorIdentification.identifiable?(err) && @node.none? { |c| ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(err, c) }
73
+
74
+ err.message.start_with?('CLUSTERDOWN Hash slot not served')
72
75
  end
73
76
 
74
77
  raise
@@ -97,7 +100,7 @@ class RedisClient
97
100
  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
98
101
  raise
99
102
  rescue ::RedisClient::CommandError => e
100
- raise unless ErrorIdentification.client_owns_error?(e, node)
103
+ raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
101
104
 
102
105
  if e.message.start_with?('MOVED')
103
106
  node = assign_redirection_node(e.message)
@@ -117,7 +120,7 @@ class RedisClient
117
120
  end
118
121
  raise
119
122
  rescue ::RedisClient::ConnectionError => e
120
- raise unless ErrorIdentification.client_owns_error?(e, node)
123
+ raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
121
124
 
122
125
  update_cluster_info!
123
126
 
@@ -315,9 +318,9 @@ class RedisClient
315
318
  def send_watch_command(command)
316
319
  raise ::RedisClient::Cluster::Transaction::ConsistencyError, 'A block required. And you need to use the block argument as a client for the transaction.' unless block_given?
317
320
 
318
- ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot|
321
+ ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
319
322
  transaction = ::RedisClient::Cluster::Transaction.new(
320
- self, @command_builder, node: c, slot: slot
323
+ self, @command_builder, node: c, slot: slot, asking: asking
321
324
  )
322
325
  yield transaction
323
326
  transaction.execute
@@ -9,7 +9,7 @@ class RedisClient
9
9
  ConsistencyError = Class.new(::RedisClient::Error)
10
10
  MAX_REDIRECTION = 2
11
11
 
12
- def initialize(router, command_builder, node: nil, slot: nil)
12
+ def initialize(router, command_builder, node: nil, slot: nil, asking: false)
13
13
  @router = router
14
14
  @command_builder = command_builder
15
15
  @retryable = true
@@ -18,6 +18,7 @@ class RedisClient
18
18
  @node = node
19
19
  prepare_tx unless @node.nil?
20
20
  @watching_slot = slot
21
+ @asking = asking
21
22
  end
22
23
 
23
24
  def call(*command, **kwargs, &block)
@@ -93,7 +94,11 @@ class RedisClient
93
94
 
94
95
  def settle
95
96
  @pipeline.call('EXEC')
96
- send_transaction(@node, redirect: MAX_REDIRECTION)
97
+ # If we needed ASKING on the watch, we need ASKING on the multi as well.
98
+ @node.call('ASKING') if @asking
99
+ # Don't handle redirections at this level if we're in a watch (the watcher handles redirections
100
+ # at the whole-transaction level.)
101
+ send_transaction(@node, redirect: !!@watching_slot ? 0 : MAX_REDIRECTION)
97
102
  end
98
103
 
99
104
  def send_transaction(client, redirect:)
@@ -110,7 +115,8 @@ class RedisClient
110
115
  client.middlewares.call_pipelined(commands, client.config) do
111
116
  connection.call_pipelined(commands, nil)
112
117
  rescue ::RedisClient::CommandError => e
113
- return handle_command_error!(commands, e, redirect: redirect) unless redirect.zero?
118
+ ensure_the_same_slot!(commands)
119
+ return handle_command_error!(e, redirect: redirect) unless redirect.zero?
114
120
 
115
121
  raise
116
122
  end
@@ -139,15 +145,13 @@ class RedisClient
139
145
  results
140
146
  end
141
147
 
142
- def handle_command_error!(commands, err, redirect:) # rubocop:disable Metrics/AbcSize
148
+ def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
143
149
  if err.message.start_with?('CROSSSLOT')
144
150
  raise ConsistencyError, "#{err.message}: #{err.command}"
145
151
  elsif err.message.start_with?('MOVED')
146
- ensure_the_same_slot!(commands)
147
152
  node = @router.assign_redirection_node(err.message)
148
153
  send_transaction(node, redirect: redirect - 1)
149
154
  elsif err.message.start_with?('ASK')
150
- ensure_the_same_slot!(commands)
151
155
  node = @router.assign_asking_node(err.message)
152
156
  try_asking(node) ? send_transaction(node, redirect: redirect - 1) : err
153
157
  else
@@ -82,9 +82,16 @@ class RedisClient
82
82
  @router.try_delegate(node, :zscan, key, *args, **kwargs, &block)
83
83
  end
84
84
 
85
- def pipelined
85
+ def pipelined(exception: true)
86
86
  seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
87
- pipeline = ::RedisClient::Cluster::Pipeline.new(@router, @command_builder, @concurrent_worker, seed: seed)
87
+ pipeline = ::RedisClient::Cluster::Pipeline.new(
88
+ @router,
89
+ @command_builder,
90
+ @concurrent_worker,
91
+ exception: exception,
92
+ seed: seed
93
+ )
94
+
88
95
  yield pipeline
89
96
  return [] if pipeline.empty?
90
97
 
@@ -98,9 +105,9 @@ class RedisClient
98
105
  return transaction.execute
99
106
  end
100
107
 
101
- ::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, slot|
108
+ ::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, slot, asking|
102
109
  transaction = ::RedisClient::Cluster::Transaction.new(
103
- @router, @command_builder, node: c, slot: slot
110
+ @router, @command_builder, node: c, slot: slot, asking: asking
104
111
  )
105
112
  yield transaction
106
113
  transaction.execute
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.11
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-20 00:00:00.000000000 Z
11
+ date: 2024-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.12'
19
+ version: '0.22'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.12'
26
+ version: '0.22'
27
27
  description:
28
28
  email:
29
29
  - proxy0721@gmail.com