redis 4.1.4 → 4.2.4
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 +35 -0
- data/README.md +9 -4
- data/lib/redis.rb +287 -248
- data/lib/redis/client.rb +71 -70
- data/lib/redis/cluster.rb +4 -0
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/option.rb +4 -1
- data/lib/redis/cluster/slot.rb +28 -14
- data/lib/redis/cluster/slot_loader.rb +2 -3
- data/lib/redis/connection.rb +1 -0
- data/lib/redis/connection/command_helper.rb +2 -2
- data/lib/redis/connection/hiredis.rb +3 -3
- data/lib/redis/connection/registry.rb +1 -1
- data/lib/redis/connection/ruby.rb +88 -94
- data/lib/redis/connection/synchrony.rb +8 -4
- data/lib/redis/distributed.rb +108 -57
- data/lib/redis/errors.rb +1 -0
- data/lib/redis/hash_ring.rb +14 -14
- data/lib/redis/pipeline.rb +6 -8
- data/lib/redis/subscribe.rb +10 -12
- data/lib/redis/version.rb +2 -1
- metadata +14 -9
data/lib/redis/connection.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Redis
|
3
4
|
module Connection
|
4
5
|
module CommandHelper
|
5
|
-
|
6
6
|
COMMAND_DELIMITER = "\r\n"
|
7
7
|
|
8
8
|
def build_command(args)
|
@@ -29,7 +29,7 @@ class Redis
|
|
29
29
|
command.join(COMMAND_DELIMITER)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
protected
|
33
33
|
|
34
34
|
def encode(string)
|
35
35
|
string.force_encoding(Encoding.default_external)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "registry"
|
3
4
|
require_relative "../errors"
|
4
5
|
require "hiredis/connection"
|
@@ -7,7 +8,6 @@ require "timeout"
|
|
7
8
|
class Redis
|
8
9
|
module Connection
|
9
10
|
class Hiredis
|
10
|
-
|
11
11
|
def self.connect(config)
|
12
12
|
connection = ::Hiredis::Connection.new
|
13
13
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -32,7 +32,7 @@ class Redis
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def connected?
|
35
|
-
@connection
|
35
|
+
@connection&.connected?
|
36
36
|
end
|
37
37
|
|
38
38
|
def timeout=(timeout)
|
@@ -58,7 +58,7 @@ class Redis
|
|
58
58
|
rescue Errno::EAGAIN
|
59
59
|
raise TimeoutError
|
60
60
|
rescue RuntimeError => err
|
61
|
-
raise ProtocolError
|
61
|
+
raise ProtocolError, err.message
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Redis
|
3
4
|
module Connection
|
4
|
-
|
5
5
|
# Store a list of loaded connection drivers in the Connection module.
|
6
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
7
7
|
# of the loaded connection drivers if the user chooses to override the
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "registry"
|
3
4
|
require_relative "command_helper"
|
4
5
|
require_relative "../errors"
|
@@ -14,8 +15,7 @@ end
|
|
14
15
|
class Redis
|
15
16
|
module Connection
|
16
17
|
module SocketMixin
|
17
|
-
|
18
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
19
19
|
|
20
20
|
def initialize(*args)
|
21
21
|
super(*args)
|
@@ -25,89 +25,77 @@ class Redis
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def timeout=(timeout)
|
28
|
-
if timeout && timeout > 0
|
29
|
-
@timeout = timeout
|
30
|
-
else
|
31
|
-
@timeout = nil
|
32
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
33
29
|
end
|
34
30
|
|
35
31
|
def write_timeout=(timeout)
|
36
|
-
if timeout && timeout > 0
|
37
|
-
@write_timeout = timeout
|
38
|
-
else
|
39
|
-
@write_timeout = nil
|
40
|
-
end
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
41
33
|
end
|
42
34
|
|
43
35
|
def read(nbytes)
|
44
36
|
result = @buffer.slice!(0, nbytes)
|
45
37
|
|
46
|
-
while result.bytesize < nbytes
|
47
|
-
result << _read_from_socket(nbytes - result.bytesize)
|
48
|
-
end
|
38
|
+
result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
|
49
39
|
|
50
40
|
result
|
51
41
|
end
|
52
42
|
|
53
43
|
def gets
|
54
|
-
crlf = nil
|
55
|
-
|
56
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
57
|
-
@buffer << _read_from_socket(16384)
|
44
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
45
|
+
@buffer << _read_from_socket(16_384)
|
58
46
|
end
|
59
47
|
|
60
48
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
61
49
|
end
|
62
50
|
|
63
51
|
def _read_from_socket(nbytes)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
raise Redis::TimeoutError
|
52
|
+
loop do
|
53
|
+
case chunk = read_nonblock(nbytes, exception: false)
|
54
|
+
when :wait_readable
|
55
|
+
unless wait_readable(@timeout)
|
56
|
+
raise Redis::TimeoutError
|
57
|
+
end
|
58
|
+
when :wait_writable
|
59
|
+
unless wait_writable(@timeout)
|
60
|
+
raise Redis::TimeoutError
|
61
|
+
end
|
62
|
+
when nil
|
63
|
+
raise Errno::ECONNRESET
|
64
|
+
when String
|
65
|
+
return chunk
|
79
66
|
end
|
80
67
|
end
|
81
|
-
|
82
|
-
rescue EOFError
|
83
|
-
raise Errno::ECONNRESET
|
84
68
|
end
|
85
69
|
|
86
70
|
def _write_to_socket(data)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
71
|
+
total_bytes_written = 0
|
72
|
+
loop do
|
73
|
+
case bytes_written = write_nonblock(data, exception: false)
|
74
|
+
when :wait_readable
|
75
|
+
unless wait_readable(@write_timeout)
|
76
|
+
raise Redis::TimeoutError
|
77
|
+
end
|
78
|
+
when :wait_writable
|
79
|
+
unless wait_writable(@write_timeout)
|
80
|
+
raise Redis::TimeoutError
|
81
|
+
end
|
82
|
+
when nil
|
83
|
+
raise Errno::ECONNRESET
|
84
|
+
when Integer
|
85
|
+
total_bytes_written += bytes_written
|
86
|
+
if bytes_written < data.bytesize
|
87
|
+
data.slice!(0, bytes_written)
|
88
|
+
else
|
89
|
+
return total_bytes_written
|
90
|
+
end
|
101
91
|
end
|
102
92
|
end
|
103
|
-
|
104
|
-
rescue EOFError
|
105
|
-
raise Errno::ECONNRESET
|
106
93
|
end
|
107
94
|
|
108
95
|
def write(data)
|
109
96
|
return super(data) unless @write_timeout
|
110
97
|
|
98
|
+
data = data.b
|
111
99
|
length = data.bytesize
|
112
100
|
total_count = 0
|
113
101
|
loop do
|
@@ -115,6 +103,7 @@ class Redis
|
|
115
103
|
|
116
104
|
total_count += count
|
117
105
|
return total_count if total_count >= length
|
106
|
+
|
118
107
|
data = data.byteslice(count..-1)
|
119
108
|
end
|
120
109
|
end
|
@@ -125,7 +114,6 @@ class Redis
|
|
125
114
|
require "timeout"
|
126
115
|
|
127
116
|
class TCPSocket < ::TCPSocket
|
128
|
-
|
129
117
|
include SocketMixin
|
130
118
|
|
131
119
|
def self.connect(host, port, timeout)
|
@@ -141,7 +129,6 @@ class Redis
|
|
141
129
|
if defined?(::UNIXSocket)
|
142
130
|
|
143
131
|
class UNIXSocket < ::UNIXSocket
|
144
|
-
|
145
132
|
include SocketMixin
|
146
133
|
|
147
134
|
def self.connect(path, timeout)
|
@@ -153,13 +140,12 @@ class Redis
|
|
153
140
|
raise TimeoutError
|
154
141
|
end
|
155
142
|
|
156
|
-
# JRuby raises Errno::EAGAIN on #read_nonblock even when
|
143
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
157
144
|
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
158
145
|
# Use the blocking #readpartial method instead.
|
159
146
|
|
160
147
|
def _read_from_socket(nbytes)
|
161
148
|
readpartial(nbytes)
|
162
|
-
|
163
149
|
rescue EOFError
|
164
150
|
raise Errno::ECONNRESET
|
165
151
|
end
|
@@ -170,19 +156,16 @@ class Redis
|
|
170
156
|
else
|
171
157
|
|
172
158
|
class TCPSocket < ::Socket
|
173
|
-
|
174
159
|
include SocketMixin
|
175
160
|
|
176
|
-
def self.connect_addrinfo(
|
177
|
-
sock = new(::Socket.const_get(
|
178
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
161
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
162
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
163
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
179
164
|
|
180
165
|
begin
|
181
166
|
sock.connect_nonblock(sockaddr)
|
182
167
|
rescue Errno::EINPROGRESS
|
183
|
-
|
184
|
-
raise TimeoutError
|
185
|
-
end
|
168
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
186
169
|
|
187
170
|
begin
|
188
171
|
sock.connect_nonblock(sockaddr)
|
@@ -221,14 +204,13 @@ class Redis
|
|
221
204
|
return connect_addrinfo(ai, port, timeout)
|
222
205
|
rescue SystemCallError
|
223
206
|
# Raise if this was our last attempt.
|
224
|
-
raise if addrinfo.length == i+1
|
207
|
+
raise if addrinfo.length == i + 1
|
225
208
|
end
|
226
209
|
end
|
227
210
|
end
|
228
211
|
end
|
229
212
|
|
230
213
|
class UNIXSocket < ::Socket
|
231
|
-
|
232
214
|
include SocketMixin
|
233
215
|
|
234
216
|
def self.connect(path, timeout)
|
@@ -238,9 +220,7 @@ class Redis
|
|
238
220
|
begin
|
239
221
|
sock.connect_nonblock(sockaddr)
|
240
222
|
rescue Errno::EINPROGRESS
|
241
|
-
|
242
|
-
raise TimeoutError
|
243
|
-
end
|
223
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
244
224
|
|
245
225
|
begin
|
246
226
|
sock.connect_nonblock(sockaddr)
|
@@ -258,12 +238,26 @@ class Redis
|
|
258
238
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
259
239
|
include SocketMixin
|
260
240
|
|
241
|
+
unless method_defined?(:wait_readable)
|
242
|
+
def wait_readable(timeout = nil)
|
243
|
+
to_io.wait_readable(timeout)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
unless method_defined?(:wait_writable)
|
248
|
+
def wait_writable(timeout = nil)
|
249
|
+
to_io.wait_writable(timeout)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
261
253
|
def self.connect(host, port, timeout, ssl_params)
|
262
254
|
# Note: this is using Redis::Connection::TCPSocket
|
263
255
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
264
256
|
|
265
257
|
ctx = OpenSSL::SSL::SSLContext.new
|
266
|
-
|
258
|
+
|
259
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
260
|
+
ctx.set_params(ssl_params || {})
|
267
261
|
|
268
262
|
ssl_sock = new(tcp_sock, ctx)
|
269
263
|
ssl_sock.hostname = host
|
@@ -277,20 +271,23 @@ class Redis
|
|
277
271
|
# Instead, you have to retry.
|
278
272
|
ssl_sock.connect_nonblock
|
279
273
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
280
|
-
if
|
274
|
+
if ssl_sock.wait_readable(timeout)
|
281
275
|
retry
|
282
276
|
else
|
283
277
|
raise TimeoutError
|
284
278
|
end
|
285
279
|
rescue IO::WaitWritable
|
286
|
-
if
|
280
|
+
if ssl_sock.wait_writable(timeout)
|
287
281
|
retry
|
288
282
|
else
|
289
283
|
raise TimeoutError
|
290
284
|
end
|
291
285
|
end
|
292
286
|
|
293
|
-
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
287
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
288
|
+
ctx.respond_to?(:verify_hostname) &&
|
289
|
+
!ctx.verify_hostname
|
290
|
+
)
|
294
291
|
ssl_sock.post_connection_check(host)
|
295
292
|
end
|
296
293
|
|
@@ -302,15 +299,16 @@ class Redis
|
|
302
299
|
class Ruby
|
303
300
|
include Redis::Connection::CommandHelper
|
304
301
|
|
305
|
-
MINUS = "-"
|
306
|
-
PLUS = "+"
|
307
|
-
COLON = ":"
|
308
|
-
DOLLAR = "$"
|
309
|
-
ASTERISK = "*"
|
302
|
+
MINUS = "-"
|
303
|
+
PLUS = "+"
|
304
|
+
COLON = ":"
|
305
|
+
DOLLAR = "$"
|
306
|
+
ASTERISK = "*"
|
310
307
|
|
311
308
|
def self.connect(config)
|
312
309
|
if config[:scheme] == "unix"
|
313
310
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
311
|
+
|
314
312
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
315
313
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
316
314
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -326,7 +324,7 @@ class Redis
|
|
326
324
|
instance
|
327
325
|
end
|
328
326
|
|
329
|
-
if [
|
327
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
330
328
|
def set_tcp_keepalive(keepalive)
|
331
329
|
return unless keepalive.is_a?(Hash)
|
332
330
|
|
@@ -338,14 +336,13 @@ class Redis
|
|
338
336
|
|
339
337
|
def get_tcp_keepalive
|
340
338
|
{
|
341
|
-
:
|
342
|
-
:
|
343
|
-
:
|
339
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
340
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
341
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
344
342
|
}
|
345
343
|
end
|
346
344
|
else
|
347
|
-
def set_tcp_keepalive(keepalive)
|
348
|
-
end
|
345
|
+
def set_tcp_keepalive(keepalive); end
|
349
346
|
|
350
347
|
def get_tcp_keepalive
|
351
348
|
{
|
@@ -354,13 +351,12 @@ class Redis
|
|
354
351
|
end
|
355
352
|
|
356
353
|
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
357
|
-
if [
|
354
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
358
355
|
def set_tcp_nodelay
|
359
356
|
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
360
357
|
end
|
361
358
|
else
|
362
|
-
def set_tcp_nodelay
|
363
|
-
end
|
359
|
+
def set_tcp_nodelay; end
|
364
360
|
end
|
365
361
|
|
366
362
|
def initialize(sock)
|
@@ -368,7 +364,7 @@ class Redis
|
|
368
364
|
end
|
369
365
|
|
370
366
|
def connected?
|
371
|
-
|
367
|
+
!!@sock
|
372
368
|
end
|
373
369
|
|
374
370
|
def disconnect
|
@@ -379,9 +375,7 @@ class Redis
|
|
379
375
|
end
|
380
376
|
|
381
377
|
def timeout=(timeout)
|
382
|
-
if @sock.respond_to?(:timeout=)
|
383
|
-
@sock.timeout = timeout
|
384
|
-
end
|
378
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
385
379
|
end
|
386
380
|
|
387
381
|
def write_timeout=(timeout)
|
@@ -396,7 +390,6 @@ class Redis
|
|
396
390
|
line = @sock.gets
|
397
391
|
reply_type = line.slice!(0, 1)
|
398
392
|
format_reply(reply_type, line)
|
399
|
-
|
400
393
|
rescue Errno::EAGAIN
|
401
394
|
raise TimeoutError
|
402
395
|
end
|
@@ -408,7 +401,7 @@ class Redis
|
|
408
401
|
when COLON then format_integer_reply(line)
|
409
402
|
when DOLLAR then format_bulk_reply(line)
|
410
403
|
when ASTERISK then format_multi_bulk_reply(line)
|
411
|
-
else raise ProtocolError
|
404
|
+
else raise ProtocolError, reply_type
|
412
405
|
end
|
413
406
|
end
|
414
407
|
|
@@ -427,6 +420,7 @@ class Redis
|
|
427
420
|
def format_bulk_reply(line)
|
428
421
|
bulklen = line.to_i
|
429
422
|
return if bulklen == -1
|
423
|
+
|
430
424
|
reply = encode(@sock.read(bulklen))
|
431
425
|
@sock.read(2) # Discard CRLF.
|
432
426
|
reply
|