redis-cluster-client 0.17.3 → 0.17.5
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 +4 -4
- data/lib/redis_client/cluster/node.rb +3 -1
- data/lib/redis_client/cluster/optimistic_locking.rb +4 -0
- data/lib/redis_client/cluster/pipeline.rb +10 -2
- data/lib/redis_client/cluster/pub_sub.rb +4 -0
- data/lib/redis_client/cluster/router.rb +69 -42
- data/lib/redis_client/cluster/transaction.rb +13 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb60940c0dbbb1bbf6497debd026596a02223668a8c7fa39b48055f5461db379
|
|
4
|
+
data.tar.gz: ffbd5eaec4b8d29637d0a4d65053039e81a08477a28d1b9e201ddba355030eae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d107b60e99745a2303b0c2f7fc702baa955f4f52a7873bd6bf11ef885dbbebdb461dd8c85f6ff4387e4460a511daed69f33a813d1dac851981dd34bb25a6128
|
|
7
|
+
data.tar.gz: bd9515f510ffe274f2e30c0fefb47729425a7a1d7a11c52f3b1ba1c331b5fd747c6a5398e553952bcf4a935f05919811e02e838ba76462b65a259e35d580ea94
|
|
@@ -155,7 +155,9 @@ class RedisClient
|
|
|
155
155
|
result_values, errors = call_multiple_nodes(@topology.clients, method, command, args, &block)
|
|
156
156
|
return result_values if errors.nil? || errors.empty?
|
|
157
157
|
|
|
158
|
-
raise ReloadNeeded if errors.values.any?
|
|
158
|
+
raise ReloadNeeded if errors.values.any? do |err|
|
|
159
|
+
err.is_a?(::RedisClient::ConnectionError) && !err.is_a?(::RedisClient::CheckoutTimeoutError)
|
|
160
|
+
end
|
|
159
161
|
|
|
160
162
|
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
|
|
161
163
|
end
|
|
@@ -34,6 +34,8 @@ class RedisClient
|
|
|
34
34
|
@router.renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
|
|
35
35
|
raise
|
|
36
36
|
end
|
|
37
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
38
|
+
raise
|
|
37
39
|
rescue ::RedisClient::ConnectionError
|
|
38
40
|
@router.renew_cluster_state
|
|
39
41
|
raise
|
|
@@ -52,6 +54,8 @@ class RedisClient
|
|
|
52
54
|
times_block_executed += 1
|
|
53
55
|
handle_asking_once(nd, &blk)
|
|
54
56
|
end
|
|
57
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
58
|
+
raise
|
|
55
59
|
rescue ::RedisClient::ConnectionError
|
|
56
60
|
# Deduct the number of retries that happened _inside_ router#handle_redirection from our remaining
|
|
57
61
|
# _external_ retries. Always deduct at least one in case handle_redirection raises without trying the block.
|
|
@@ -205,7 +205,7 @@ class RedisClient
|
|
|
205
205
|
cluster_state_errors ||= {}
|
|
206
206
|
cluster_state_errors[node_key] = v
|
|
207
207
|
when StandardError
|
|
208
|
-
cluster_state_errors ||= {} if v.is_a?(::RedisClient::ConnectionError)
|
|
208
|
+
cluster_state_errors ||= {} if v.is_a?(::RedisClient::ConnectionError) && !v.is_a?(::RedisClient::CheckoutTimeoutError)
|
|
209
209
|
errors ||= {}
|
|
210
210
|
errors[node_key] = v
|
|
211
211
|
else
|
|
@@ -277,10 +277,16 @@ class RedisClient
|
|
|
277
277
|
try_redirection(node, pipeline, inner_index)
|
|
278
278
|
elsif err.message.start_with?('ASK')
|
|
279
279
|
node = @router.assign_asking_node(err.message)
|
|
280
|
-
|
|
280
|
+
node.with do |client|
|
|
281
|
+
try_asking(client) ? try_redirection(client, pipeline, inner_index) : err
|
|
282
|
+
end
|
|
281
283
|
else
|
|
282
284
|
err
|
|
283
285
|
end
|
|
286
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
287
|
+
raise if @exception
|
|
288
|
+
|
|
289
|
+
err
|
|
284
290
|
end
|
|
285
291
|
|
|
286
292
|
def try_redirection(node, pipeline, inner_index)
|
|
@@ -305,6 +311,8 @@ class RedisClient
|
|
|
305
311
|
|
|
306
312
|
def try_asking(node)
|
|
307
313
|
node.call('asking') == 'OK'
|
|
314
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
315
|
+
raise
|
|
308
316
|
rescue StandardError
|
|
309
317
|
false
|
|
310
318
|
end
|
|
@@ -171,6 +171,8 @@ class RedisClient
|
|
|
171
171
|
|
|
172
172
|
def handle_connection_error(node_key, ignore: false)
|
|
173
173
|
yield
|
|
174
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
175
|
+
raise
|
|
174
176
|
rescue ::RedisClient::ConnectionError
|
|
175
177
|
@state_dict[node_key]&.close
|
|
176
178
|
@state_dict.delete(node_key)
|
|
@@ -186,6 +188,8 @@ class RedisClient
|
|
|
186
188
|
@state_dict.clear
|
|
187
189
|
@commands.each { |command| _call(command) }
|
|
188
190
|
break
|
|
191
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
192
|
+
raise
|
|
189
193
|
rescue ::RedisClient::ConnectionError, ::RedisClient::Cluster::NodeMightBeDown
|
|
190
194
|
attempt += 1
|
|
191
195
|
raise if attempt >= 10
|
|
@@ -42,7 +42,7 @@ class RedisClient
|
|
|
42
42
|
return send_command_by_spec(method, command, args, &block) if action.nil?
|
|
43
43
|
|
|
44
44
|
send_command_by_action(action, method, command, args, &block)
|
|
45
|
-
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
|
45
|
+
rescue ::RedisClient::CircuitBreaker::OpenCircuitError, ::RedisClient::CheckoutTimeoutError
|
|
46
46
|
raise
|
|
47
47
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
|
48
48
|
renew_cluster_state
|
|
@@ -58,6 +58,7 @@ class RedisClient
|
|
|
58
58
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
|
59
59
|
|
|
60
60
|
renew_cluster_state if e.errors.values.any? do |err|
|
|
61
|
+
next false if err.is_a?(::RedisClient::CheckoutTimeoutError)
|
|
61
62
|
next false if ::RedisClient::Cluster::ErrorIdentification.identifiable?(err) && @node.none? { |c| ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(err, c) }
|
|
62
63
|
|
|
63
64
|
err.message.start_with?('CLUSTERDOWN') || err.is_a?(::RedisClient::ConnectionError)
|
|
@@ -84,46 +85,56 @@ class RedisClient
|
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
asking = false
|
|
89
|
+
begin
|
|
90
|
+
if asking
|
|
91
|
+
node.with do |client|
|
|
92
|
+
client.call('asking')
|
|
93
|
+
yield client
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
yield node
|
|
97
|
+
end
|
|
98
|
+
rescue ::RedisClient::CircuitBreaker::OpenCircuitError, ::RedisClient::CheckoutTimeoutError
|
|
99
|
+
raise
|
|
100
|
+
rescue ::RedisClient::CommandError => e
|
|
101
|
+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
|
102
|
+
|
|
103
|
+
retry_count -= 1
|
|
104
|
+
asking = false
|
|
105
|
+
if e.message.start_with?('MOVED')
|
|
106
|
+
node = assign_redirection_node(e.message)
|
|
107
|
+
retry if retry_count >= 0
|
|
108
|
+
elsif e.message.start_with?('ASK')
|
|
109
|
+
node = assign_asking_node(e.message)
|
|
110
|
+
asking = true
|
|
111
|
+
retry if retry_count >= 0
|
|
112
|
+
elsif e.message.start_with?('CLUSTERDOWN')
|
|
113
|
+
renew_cluster_state
|
|
114
|
+
retry if retry_count >= 0
|
|
102
115
|
end
|
|
103
|
-
elsif e.message.start_with?('CLUSTERDOWN')
|
|
104
|
-
renew_cluster_state
|
|
105
|
-
retry if retry_count >= 0
|
|
106
|
-
end
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
raise
|
|
118
|
+
rescue ::RedisClient::ConnectionError => e
|
|
119
|
+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
renew_cluster_state
|
|
122
|
+
raise if command.nil? || command.empty?
|
|
114
123
|
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
retry_count -= 1
|
|
125
|
+
raise if retry_count < 0
|
|
117
126
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
# Find the node to use for this command - if this fails for some reason, though, re-use
|
|
128
|
+
# the old node.
|
|
129
|
+
begin
|
|
130
|
+
node = find_node(find_node_key(command))
|
|
131
|
+
rescue StandardError
|
|
132
|
+
raise e
|
|
133
|
+
end
|
|
125
134
|
|
|
126
|
-
|
|
135
|
+
asking = false
|
|
136
|
+
retry
|
|
137
|
+
end
|
|
127
138
|
end
|
|
128
139
|
|
|
129
140
|
def scan(command, seed: nil, cursor_index: 1) # rubocop:disable Metrics/AbcSize
|
|
@@ -146,6 +157,8 @@ class RedisClient
|
|
|
146
157
|
client_index += 1 if result_cursor == 0
|
|
147
158
|
|
|
148
159
|
[((result_cursor << 8) + client_index).to_s, result_keys]
|
|
160
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
161
|
+
raise
|
|
149
162
|
rescue ::RedisClient::ConnectionError
|
|
150
163
|
renew_cluster_state
|
|
151
164
|
raise
|
|
@@ -351,7 +364,8 @@ class RedisClient
|
|
|
351
364
|
elsif command[1].casecmp('purge').zero?
|
|
352
365
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
353
366
|
else
|
|
354
|
-
|
|
367
|
+
# The other subcommands keep the default routing with the redirection handling.
|
|
368
|
+
send_command_by_spec(method, command, args, &block)
|
|
355
369
|
end
|
|
356
370
|
end
|
|
357
371
|
|
|
@@ -360,12 +374,15 @@ class RedisClient
|
|
|
360
374
|
@node.call_all(method, command, args, &block).flatten
|
|
361
375
|
elsif command[1].casecmp('pause').zero?
|
|
362
376
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
377
|
+
elsif command[1].casecmp('unpause').zero?
|
|
378
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
363
379
|
elsif command[1].casecmp('reply').zero?
|
|
364
380
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
365
381
|
elsif command[1].casecmp('setname').zero?
|
|
366
382
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
367
383
|
else
|
|
368
|
-
|
|
384
|
+
# The other subcommands keep the default routing with the command tips.
|
|
385
|
+
send_command_by_spec(method, command, args, &block)
|
|
369
386
|
end
|
|
370
387
|
end
|
|
371
388
|
|
|
@@ -376,8 +393,15 @@ class RedisClient
|
|
|
376
393
|
command[1].casecmp('slots').zero? ||
|
|
377
394
|
command[1].casecmp('shards').zero? ||
|
|
378
395
|
command[1].casecmp('count-failure-reports').zero? ||
|
|
379
|
-
command[1].casecmp('slaves').zero?
|
|
396
|
+
command[1].casecmp('slaves').zero? ||
|
|
397
|
+
command[1].casecmp('replicas').zero? ||
|
|
398
|
+
command[1].casecmp('myid').zero? ||
|
|
399
|
+
command[1].casecmp('myshardid').zero? ||
|
|
400
|
+
command[1].casecmp('links').zero?
|
|
380
401
|
assign_node(command).public_send(method, *args, command, &block)
|
|
402
|
+
elsif command[1].casecmp('slot-stats').zero?
|
|
403
|
+
# Each primary replies with the statistics of its own slots only.
|
|
404
|
+
@node.call_primaries(method, command, args).flatten(1).then(&TSF.call(block))
|
|
381
405
|
elsif command[1].casecmp('saveconfig').zero?
|
|
382
406
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
383
407
|
elsif command[1].casecmp('countkeysinslot').zero? || command[1].casecmp('getkeysinslot').zero?
|
|
@@ -394,15 +418,18 @@ class RedisClient
|
|
|
394
418
|
if command[1].casecmp('debug').zero?
|
|
395
419
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
396
420
|
elsif command[1].casecmp('kill').zero?
|
|
397
|
-
|
|
421
|
+
# The primaries without any running script reply with the NOTBUSY error.
|
|
422
|
+
@node.call_primaries_leniently(method, command, args).first.then(&TSF.call(block))
|
|
398
423
|
elsif command[1].casecmp('flush').zero?
|
|
399
|
-
|
|
424
|
+
# The redis 7.0 or later doesn't propagate the scripts to the replicas.
|
|
425
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
400
426
|
elsif command[1].casecmp('load').zero?
|
|
401
|
-
@node.
|
|
427
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
|
402
428
|
elsif command[1].casecmp('exists').zero?
|
|
403
429
|
@node.call_all(method, command, args).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }.then(&TSF.call(block))
|
|
404
430
|
else
|
|
405
|
-
|
|
431
|
+
# The other subcommands keep the default routing with the redirection handling.
|
|
432
|
+
send_command_by_spec(method, command, args, &block)
|
|
406
433
|
end
|
|
407
434
|
end
|
|
408
435
|
|
|
@@ -124,22 +124,27 @@ class RedisClient
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
def send_pipeline(client, redirect:) # rubocop:disable Metrics/AbcSize
|
|
127
|
+
def send_pipeline(client, redirect:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
128
128
|
replies = client.ensure_connected_cluster_scoped(retryable: @retryable) do |connection|
|
|
129
129
|
commands = @pipeline._commands
|
|
130
130
|
client.middlewares.call_pipelined(commands, client.config) do
|
|
131
131
|
connection.call_pipelined(commands, nil)
|
|
132
132
|
rescue ::RedisClient::CommandError => e
|
|
133
133
|
ensure_the_same_slot!(commands)
|
|
134
|
-
|
|
134
|
+
raise if redirect.zero?
|
|
135
|
+
# Follow the redirection after leaving this block, so that errors from the other node don't close or retry this connection.
|
|
136
|
+
break e if e.message.start_with?('MOVED', 'ASK')
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
handle_command_error!(e, redirect: redirect)
|
|
137
139
|
end
|
|
138
140
|
end
|
|
139
141
|
|
|
142
|
+
return handle_command_error!(replies, redirect: redirect) if replies.is_a?(::RedisClient::CommandError)
|
|
140
143
|
return if replies.last.nil?
|
|
141
144
|
|
|
142
145
|
coerce_results!(replies.last)
|
|
146
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
147
|
+
raise
|
|
143
148
|
rescue ::RedisClient::ConnectionError
|
|
144
149
|
@router.renew_cluster_state if @watching_slot.nil?
|
|
145
150
|
raise
|
|
@@ -171,7 +176,9 @@ class RedisClient
|
|
|
171
176
|
send_transaction(node, redirect: redirect - 1)
|
|
172
177
|
elsif err.message.start_with?('ASK')
|
|
173
178
|
node = @router.assign_asking_node(err.message)
|
|
174
|
-
|
|
179
|
+
node.with do |client|
|
|
180
|
+
try_asking(client) ? send_transaction(client, redirect: redirect - 1) : raise(err)
|
|
181
|
+
end
|
|
175
182
|
elsif err.message.start_with?('CLUSTERDOWN')
|
|
176
183
|
@router.renew_cluster_state if @watching_slot.nil?
|
|
177
184
|
raise err
|
|
@@ -190,6 +197,8 @@ class RedisClient
|
|
|
190
197
|
|
|
191
198
|
def try_asking(node)
|
|
192
199
|
node.call('asking') == 'OK'
|
|
200
|
+
rescue ::RedisClient::CheckoutTimeoutError
|
|
201
|
+
raise
|
|
193
202
|
rescue StandardError
|
|
194
203
|
false
|
|
195
204
|
end
|