redis-cluster-client 0.7.10 → 0.8.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: b591c92886bf877147317dd04695d2d22354958443f83e386b061b45b0407b28
4
- data.tar.gz: 40a46369e32c60a3c165aff646d3dbd53c3ef427fef6220d4bacc17fc6a97232
3
+ metadata.gz: b27b124d30b25712726c2d6471fffe4fd879de2dabb2002d0654e40b1e24b90e
4
+ data.tar.gz: eae0cd4ff3d6ab6317f7fff7493df1da9fb4386b4fbd187619d41e0bdf891d56
5
5
  SHA512:
6
- metadata.gz: 0e8eda475d22314feb84d16c2d5d75dfe714abecf2c6482a7600c012d02705110c40a0ed7717029a7e8ad154040c6cd44978ffd6b9a2c0350ec33e45d1e1b81e
7
- data.tar.gz: a8fc2bb1febdac9e44c23aaab93ddce567d9ea1fe4ff09f74aa3ebac7eba56efceff44b5f13fea0284d4bec8b2677bd58dfee0eedfebe9cdc4ed9e7d4f6cb6df
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)
@@ -313,9 +313,11 @@ class RedisClient
313
313
 
314
314
  # for redis-rb
315
315
  def send_watch_command(command)
316
- ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot|
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
+
318
+ ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
317
319
  transaction = ::RedisClient::Cluster::Transaction.new(
318
- self, @command_builder, node: c, slot: slot
320
+ self, @command_builder, node: c, slot: slot, asking: asking
319
321
  )
320
322
  yield transaction
321
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.10
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