redis 4.1.2 → 4.2.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 +4 -4
- data/CHANGELOG.md +28 -1
- data/README.md +14 -5
- data/lib/redis/client.rb +66 -69
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/cluster.rb +13 -4
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +47 -58
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +81 -55
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +361 -342
- metadata +5 -5
data/lib/redis/cluster.rb
CHANGED
@@ -80,6 +80,7 @@ class Redis
|
|
80
80
|
def call_pipeline(pipeline)
|
81
81
|
node_keys, command_keys = extract_keys_in_pipeline(pipeline)
|
82
82
|
raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
|
83
|
+
|
83
84
|
node = find_node(node_keys.first)
|
84
85
|
try_send(node, :call_pipeline, pipeline)
|
85
86
|
end
|
@@ -112,12 +113,11 @@ class Redis
|
|
112
113
|
node = Node.new(option.per_node_key)
|
113
114
|
available_slots = SlotLoader.load(node)
|
114
115
|
node_flags = NodeLoader.load_flags(node)
|
115
|
-
|
116
|
-
option.update_node(available_node_urls)
|
116
|
+
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
|
117
117
|
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
118
118
|
Slot.new(available_slots, node_flags, option.use_replica?)]
|
119
119
|
ensure
|
120
|
-
node
|
120
|
+
node&.each(&:disconnect)
|
121
121
|
end
|
122
122
|
|
123
123
|
def fetch_command_details(nodes)
|
@@ -216,9 +216,14 @@ class Redis
|
|
216
216
|
node.public_send(method_name, *args, &block)
|
217
217
|
rescue CommandError => err
|
218
218
|
if err.message.start_with?('MOVED')
|
219
|
-
|
219
|
+
raise if retry_count <= 0
|
220
|
+
|
221
|
+
node = assign_redirection_node(err.message)
|
222
|
+
retry_count -= 1
|
223
|
+
retry
|
220
224
|
elsif err.message.start_with?('ASK')
|
221
225
|
raise if retry_count <= 0
|
226
|
+
|
222
227
|
node = assign_asking_node(err.message)
|
223
228
|
node.call(%i[asking])
|
224
229
|
retry_count -= 1
|
@@ -226,6 +231,9 @@ class Redis
|
|
226
231
|
else
|
227
232
|
raise
|
228
233
|
end
|
234
|
+
rescue CannotConnectError
|
235
|
+
update_cluster_info!
|
236
|
+
raise
|
229
237
|
end
|
230
238
|
|
231
239
|
def assign_redirection_node(err_msg)
|
@@ -261,6 +269,7 @@ class Redis
|
|
261
269
|
|
262
270
|
def find_node(node_key)
|
263
271
|
return @node.sample if node_key.nil?
|
272
|
+
|
264
273
|
@node.find_by(node_key)
|
265
274
|
rescue Node::ReloadNeeded
|
266
275
|
update_cluster_info!(node_key)
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
5
|
module CommandHelper
|
4
|
-
|
5
6
|
COMMAND_DELIMITER = "\r\n"
|
6
7
|
|
7
8
|
def build_command(args)
|
@@ -28,7 +29,7 @@ class Redis
|
|
28
29
|
command.join(COMMAND_DELIMITER)
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
protected
|
32
33
|
|
33
34
|
def encode(string)
|
34
35
|
string.force_encoding(Encoding.default_external)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "../errors"
|
3
5
|
require "hiredis/connection"
|
@@ -6,7 +8,6 @@ require "timeout"
|
|
6
8
|
class Redis
|
7
9
|
module Connection
|
8
10
|
class Hiredis
|
9
|
-
|
10
11
|
def self.connect(config)
|
11
12
|
connection = ::Hiredis::Connection.new
|
12
13
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -31,7 +32,7 @@ class Redis
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def connected?
|
34
|
-
@connection
|
35
|
+
@connection&.connected?
|
35
36
|
end
|
36
37
|
|
37
38
|
def timeout=(timeout)
|
@@ -57,7 +58,7 @@ class Redis
|
|
57
58
|
rescue Errno::EAGAIN
|
58
59
|
raise TimeoutError
|
59
60
|
rescue RuntimeError => err
|
60
|
-
raise ProtocolError
|
61
|
+
raise ProtocolError, err.message
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
|
-
|
4
5
|
# Store a list of loaded connection drivers in the Connection module.
|
5
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
6
7
|
# of the loaded connection drivers if the user chooses to override the
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "command_helper"
|
3
5
|
require_relative "../errors"
|
@@ -13,8 +15,7 @@ end
|
|
13
15
|
class Redis
|
14
16
|
module Connection
|
15
17
|
module SocketMixin
|
16
|
-
|
17
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
18
19
|
|
19
20
|
def initialize(*args)
|
20
21
|
super(*args)
|
@@ -24,46 +25,32 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def timeout=(timeout)
|
27
|
-
if timeout && timeout > 0
|
28
|
-
@timeout = timeout
|
29
|
-
else
|
30
|
-
@timeout = nil
|
31
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
32
29
|
end
|
33
30
|
|
34
31
|
def write_timeout=(timeout)
|
35
|
-
if timeout && timeout > 0
|
36
|
-
@write_timeout = timeout
|
37
|
-
else
|
38
|
-
@write_timeout = nil
|
39
|
-
end
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
40
33
|
end
|
41
34
|
|
42
35
|
def read(nbytes)
|
43
36
|
result = @buffer.slice!(0, nbytes)
|
44
37
|
|
45
|
-
while result.bytesize < nbytes
|
46
|
-
result << _read_from_socket(nbytes - result.bytesize)
|
47
|
-
end
|
38
|
+
result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
|
48
39
|
|
49
40
|
result
|
50
41
|
end
|
51
42
|
|
52
43
|
def gets
|
53
|
-
crlf = nil
|
54
|
-
|
55
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
56
|
-
@buffer << _read_from_socket(1024)
|
44
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
45
|
+
@buffer << _read_from_socket(16_384)
|
57
46
|
end
|
58
47
|
|
59
48
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
60
49
|
end
|
61
50
|
|
62
51
|
def _read_from_socket(nbytes)
|
63
|
-
|
64
52
|
begin
|
65
53
|
read_nonblock(nbytes)
|
66
|
-
|
67
54
|
rescue IO::WaitReadable
|
68
55
|
if IO.select([self], nil, nil, @timeout)
|
69
56
|
retry
|
@@ -77,7 +64,6 @@ class Redis
|
|
77
64
|
raise Redis::TimeoutError
|
78
65
|
end
|
79
66
|
end
|
80
|
-
|
81
67
|
rescue EOFError
|
82
68
|
raise Errno::ECONNRESET
|
83
69
|
end
|
@@ -85,7 +71,6 @@ class Redis
|
|
85
71
|
def _write_to_socket(data)
|
86
72
|
begin
|
87
73
|
write_nonblock(data)
|
88
|
-
|
89
74
|
rescue IO::WaitWritable
|
90
75
|
if IO.select(nil, [self], nil, @write_timeout)
|
91
76
|
retry
|
@@ -99,7 +84,6 @@ class Redis
|
|
99
84
|
raise Redis::TimeoutError
|
100
85
|
end
|
101
86
|
end
|
102
|
-
|
103
87
|
rescue EOFError
|
104
88
|
raise Errno::ECONNRESET
|
105
89
|
end
|
@@ -114,6 +98,7 @@ class Redis
|
|
114
98
|
|
115
99
|
total_count += count
|
116
100
|
return total_count if total_count >= length
|
101
|
+
|
117
102
|
data = data.byteslice(count..-1)
|
118
103
|
end
|
119
104
|
end
|
@@ -124,7 +109,6 @@ class Redis
|
|
124
109
|
require "timeout"
|
125
110
|
|
126
111
|
class TCPSocket < ::TCPSocket
|
127
|
-
|
128
112
|
include SocketMixin
|
129
113
|
|
130
114
|
def self.connect(host, port, timeout)
|
@@ -140,7 +124,6 @@ class Redis
|
|
140
124
|
if defined?(::UNIXSocket)
|
141
125
|
|
142
126
|
class UNIXSocket < ::UNIXSocket
|
143
|
-
|
144
127
|
include SocketMixin
|
145
128
|
|
146
129
|
def self.connect(path, timeout)
|
@@ -158,7 +141,6 @@ class Redis
|
|
158
141
|
|
159
142
|
def _read_from_socket(nbytes)
|
160
143
|
readpartial(nbytes)
|
161
|
-
|
162
144
|
rescue EOFError
|
163
145
|
raise Errno::ECONNRESET
|
164
146
|
end
|
@@ -169,19 +151,16 @@ class Redis
|
|
169
151
|
else
|
170
152
|
|
171
153
|
class TCPSocket < ::Socket
|
172
|
-
|
173
154
|
include SocketMixin
|
174
155
|
|
175
|
-
def self.connect_addrinfo(
|
176
|
-
sock = new(::Socket.const_get(
|
177
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
156
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
157
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
158
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
178
159
|
|
179
160
|
begin
|
180
161
|
sock.connect_nonblock(sockaddr)
|
181
162
|
rescue Errno::EINPROGRESS
|
182
|
-
if IO.select(nil, [sock], nil, timeout)
|
183
|
-
raise TimeoutError
|
184
|
-
end
|
163
|
+
raise TimeoutError if IO.select(nil, [sock], nil, timeout).nil?
|
185
164
|
|
186
165
|
begin
|
187
166
|
sock.connect_nonblock(sockaddr)
|
@@ -220,14 +199,13 @@ class Redis
|
|
220
199
|
return connect_addrinfo(ai, port, timeout)
|
221
200
|
rescue SystemCallError
|
222
201
|
# Raise if this was our last attempt.
|
223
|
-
raise if addrinfo.length == i+1
|
202
|
+
raise if addrinfo.length == i + 1
|
224
203
|
end
|
225
204
|
end
|
226
205
|
end
|
227
206
|
end
|
228
207
|
|
229
208
|
class UNIXSocket < ::Socket
|
230
|
-
|
231
209
|
include SocketMixin
|
232
210
|
|
233
211
|
def self.connect(path, timeout)
|
@@ -237,9 +215,7 @@ class Redis
|
|
237
215
|
begin
|
238
216
|
sock.connect_nonblock(sockaddr)
|
239
217
|
rescue Errno::EINPROGRESS
|
240
|
-
if IO.select(nil, [sock], nil, timeout)
|
241
|
-
raise TimeoutError
|
242
|
-
end
|
218
|
+
raise TimeoutError if IO.select(nil, [sock], nil, timeout).nil?
|
243
219
|
|
244
220
|
begin
|
245
221
|
sock.connect_nonblock(sockaddr)
|
@@ -262,7 +238,9 @@ class Redis
|
|
262
238
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
263
239
|
|
264
240
|
ctx = OpenSSL::SSL::SSLContext.new
|
265
|
-
|
241
|
+
|
242
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
243
|
+
ctx.set_params(ssl_params || {})
|
266
244
|
|
267
245
|
ssl_sock = new(tcp_sock, ctx)
|
268
246
|
ssl_sock.hostname = host
|
@@ -289,7 +267,10 @@ class Redis
|
|
289
267
|
end
|
290
268
|
end
|
291
269
|
|
292
|
-
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
270
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
271
|
+
ctx.respond_to?(:verify_hostname) &&
|
272
|
+
!ctx.verify_hostname
|
273
|
+
)
|
293
274
|
ssl_sock.post_connection_check(host)
|
294
275
|
end
|
295
276
|
|
@@ -301,15 +282,16 @@ class Redis
|
|
301
282
|
class Ruby
|
302
283
|
include Redis::Connection::CommandHelper
|
303
284
|
|
304
|
-
MINUS = "-"
|
305
|
-
PLUS = "+"
|
306
|
-
COLON = ":"
|
307
|
-
DOLLAR = "$"
|
308
|
-
ASTERISK = "*"
|
285
|
+
MINUS = "-"
|
286
|
+
PLUS = "+"
|
287
|
+
COLON = ":"
|
288
|
+
DOLLAR = "$"
|
289
|
+
ASTERISK = "*"
|
309
290
|
|
310
291
|
def self.connect(config)
|
311
292
|
if config[:scheme] == "unix"
|
312
293
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
294
|
+
|
313
295
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
314
296
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
315
297
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -321,10 +303,11 @@ class Redis
|
|
321
303
|
instance.timeout = config[:read_timeout]
|
322
304
|
instance.write_timeout = config[:write_timeout]
|
323
305
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
306
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
324
307
|
instance
|
325
308
|
end
|
326
309
|
|
327
|
-
if [
|
310
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
328
311
|
def set_tcp_keepalive(keepalive)
|
329
312
|
return unless keepalive.is_a?(Hash)
|
330
313
|
|
@@ -336,14 +319,13 @@ class Redis
|
|
336
319
|
|
337
320
|
def get_tcp_keepalive
|
338
321
|
{
|
339
|
-
:
|
340
|
-
:
|
341
|
-
:
|
322
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
323
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
324
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
342
325
|
}
|
343
326
|
end
|
344
327
|
else
|
345
|
-
def set_tcp_keepalive(keepalive)
|
346
|
-
end
|
328
|
+
def set_tcp_keepalive(keepalive); end
|
347
329
|
|
348
330
|
def get_tcp_keepalive
|
349
331
|
{
|
@@ -351,12 +333,21 @@ class Redis
|
|
351
333
|
end
|
352
334
|
end
|
353
335
|
|
336
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
337
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
338
|
+
def set_tcp_nodelay
|
339
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
340
|
+
end
|
341
|
+
else
|
342
|
+
def set_tcp_nodelay; end
|
343
|
+
end
|
344
|
+
|
354
345
|
def initialize(sock)
|
355
346
|
@sock = sock
|
356
347
|
end
|
357
348
|
|
358
349
|
def connected?
|
359
|
-
|
350
|
+
!!@sock
|
360
351
|
end
|
361
352
|
|
362
353
|
def disconnect
|
@@ -367,9 +358,7 @@ class Redis
|
|
367
358
|
end
|
368
359
|
|
369
360
|
def timeout=(timeout)
|
370
|
-
if @sock.respond_to?(:timeout=)
|
371
|
-
@sock.timeout = timeout
|
372
|
-
end
|
361
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
373
362
|
end
|
374
363
|
|
375
364
|
def write_timeout=(timeout)
|
@@ -384,7 +373,6 @@ class Redis
|
|
384
373
|
line = @sock.gets
|
385
374
|
reply_type = line.slice!(0, 1)
|
386
375
|
format_reply(reply_type, line)
|
387
|
-
|
388
376
|
rescue Errno::EAGAIN
|
389
377
|
raise TimeoutError
|
390
378
|
end
|
@@ -396,7 +384,7 @@ class Redis
|
|
396
384
|
when COLON then format_integer_reply(line)
|
397
385
|
when DOLLAR then format_bulk_reply(line)
|
398
386
|
when ASTERISK then format_multi_bulk_reply(line)
|
399
|
-
else raise ProtocolError
|
387
|
+
else raise ProtocolError, reply_type
|
400
388
|
end
|
401
389
|
end
|
402
390
|
|
@@ -415,6 +403,7 @@ class Redis
|
|
415
403
|
def format_bulk_reply(line)
|
416
404
|
bulklen = line.to_i
|
417
405
|
return if bulklen == -1
|
406
|
+
|
418
407
|
reply = encode(@sock.read(bulklen))
|
419
408
|
@sock.read(2) # Discard CRLF.
|
420
409
|
reply
|
@@ -1,9 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "command_helper"
|
2
4
|
require_relative "registry"
|
3
5
|
require_relative "../errors"
|
4
6
|
require "em-synchrony"
|
5
7
|
require "hiredis/reader"
|
6
8
|
|
9
|
+
Kernel.warn(
|
10
|
+
"The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
|
11
|
+
"We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
|
12
|
+
)
|
13
|
+
|
7
14
|
class Redis
|
8
15
|
module Connection
|
9
16
|
class RedisClient < EventMachine::Connection
|
@@ -46,9 +53,7 @@ class Redis
|
|
46
53
|
|
47
54
|
def read
|
48
55
|
@req = EventMachine::DefaultDeferrable.new
|
49
|
-
if @timeout > 0
|
50
|
-
@req.timeout(@timeout, :timeout)
|
51
|
-
end
|
56
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
52
57
|
EventMachine::Synchrony.sync @req
|
53
58
|
end
|
54
59
|
|
@@ -105,7 +110,7 @@ class Redis
|
|
105
110
|
end
|
106
111
|
|
107
112
|
def connected?
|
108
|
-
@connection
|
113
|
+
@connection&.connected?
|
109
114
|
end
|
110
115
|
|
111
116
|
def timeout=(timeout)
|