redis-cluster-client 0.7.11 → 0.8.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: 84e0a41582397ffdb79a5bad130f849ef3980dd00e602908c76006224e0663ed
4
- data.tar.gz: e987165bfcbbdda0ef5c122a88d36c21ebf7ade0ea6f8009203b7bb8a87a2736
3
+ metadata.gz: b27b124d30b25712726c2d6471fffe4fd879de2dabb2002d0654e40b1e24b90e
4
+ data.tar.gz: eae0cd4ff3d6ab6317f7fff7493df1da9fb4386b4fbd187619d41e0bdf891d56
5
5
  SHA512:
6
- metadata.gz: 2bd0e3de07bad00ea4ade79f397c75ee4e9b244655bd3f5bb6ecfec9a25ca0c3893fb9fa135eb16d01b0c1b7747375c4e5d37c3683befe47fcc34bbd0c34d619
7
- data.tar.gz: 5f84cd60815f57769ba3750f6f4e644ed4cdd933b5318ea4aa5ae725711c8ed87ea14e2ef252c92c66589645131e7a0a6381b2b8411fa56475768d2326cc69e6
6
+ metadata.gz: a353a42631431794e3d1d9616810ef5378930765683013cb96c8e49c010434e2f3f08995b0e48601492791b577612a9bf688dd0b186b72b0bba54e0330f24e8f
7
+ data.tar.gz: f87fed71fda0b9cf9ab3dde1b402f8c4b0c921c85efe8d794ef9fee7a48d143f43b829e6f2cd6ca01532f0c8c6ca8f682195ea3673aefc9ba5d342a9871a6c75
@@ -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)
@@ -315,9 +315,9 @@ class RedisClient
315
315
  def send_watch_command(command)
316
316
  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
317
 
318
- ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot|
318
+ ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
319
319
  transaction = ::RedisClient::Cluster::Transaction.new(
320
- self, @command_builder, node: c, slot: slot
320
+ self, @command_builder, node: c, slot: slot, asking: asking
321
321
  )
322
322
  yield transaction
323
323
  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.0
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-12 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