redis 4.1.4 → 4.7.1
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 +141 -0
- data/README.md +52 -27
- data/lib/redis/client.rb +122 -87
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +8 -9
- data/lib/redis/cluster/node.rb +17 -1
- data/lib/redis/cluster/node_loader.rb +8 -11
- data/lib/redis/cluster/option.rb +18 -5
- data/lib/redis/cluster/slot.rb +28 -14
- data/lib/redis/cluster/slot_loader.rb +11 -15
- data/lib/redis/cluster.rb +37 -13
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +411 -0
- data/lib/redis/commands/lists.rb +289 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +207 -0
- data/lib/redis/commands/sorted_sets.rb +812 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +139 -0
- data/lib/redis/commands.rb +242 -0
- data/lib/redis/connection/command_helper.rb +4 -2
- data/lib/redis/connection/hiredis.rb +6 -7
- data/lib/redis/connection/registry.rb +1 -1
- data/lib/redis/connection/ruby.rb +106 -114
- data/lib/redis/connection/synchrony.rb +16 -10
- data/lib/redis/connection.rb +2 -1
- data/lib/redis/distributed.rb +200 -65
- data/lib/redis/errors.rb +10 -0
- data/lib/redis/hash_ring.rb +14 -14
- data/lib/redis/pipeline.rb +133 -10
- data/lib/redis/subscribe.rb +10 -12
- data/lib/redis/version.rb +2 -1
- data/lib/redis.rb +158 -3358
- metadata +32 -10
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
require "redis/connection/registry"
|
4
|
+
require "redis/connection/command_helper"
|
5
|
+
require "redis/errors"
|
6
|
+
|
5
7
|
require "socket"
|
6
8
|
require "timeout"
|
7
9
|
|
@@ -14,108 +16,85 @@ end
|
|
14
16
|
class Redis
|
15
17
|
module Connection
|
16
18
|
module SocketMixin
|
17
|
-
|
18
|
-
CRLF = "\r\n".freeze
|
19
|
+
CRLF = "\r\n"
|
19
20
|
|
20
21
|
def initialize(*args)
|
21
22
|
super(*args)
|
22
23
|
|
23
24
|
@timeout = @write_timeout = nil
|
24
|
-
@buffer = "".
|
25
|
+
@buffer = "".b
|
25
26
|
end
|
26
27
|
|
27
28
|
def timeout=(timeout)
|
28
|
-
if timeout && timeout > 0
|
29
|
-
@timeout = timeout
|
30
|
-
else
|
31
|
-
@timeout = nil
|
32
|
-
end
|
29
|
+
@timeout = (timeout if timeout && timeout > 0)
|
33
30
|
end
|
34
31
|
|
35
32
|
def write_timeout=(timeout)
|
36
|
-
if timeout && timeout > 0
|
37
|
-
@write_timeout = timeout
|
38
|
-
else
|
39
|
-
@write_timeout = nil
|
40
|
-
end
|
33
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
41
34
|
end
|
42
35
|
|
43
36
|
def read(nbytes)
|
44
37
|
result = @buffer.slice!(0, nbytes)
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
39
|
+
buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
|
40
|
+
result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
|
49
41
|
|
50
42
|
result
|
51
43
|
end
|
52
44
|
|
53
45
|
def gets
|
54
|
-
crlf = nil
|
55
|
-
|
56
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
57
|
-
@buffer << _read_from_socket(16384)
|
46
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
47
|
+
@buffer << _read_from_socket(16_384)
|
58
48
|
end
|
59
49
|
|
60
50
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
61
51
|
end
|
62
52
|
|
63
|
-
def _read_from_socket(nbytes)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
raise Redis::TimeoutError
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
rescue EOFError
|
83
|
-
raise Errno::ECONNRESET
|
84
|
-
end
|
85
|
-
|
86
|
-
def _write_to_socket(data)
|
87
|
-
begin
|
88
|
-
write_nonblock(data)
|
89
|
-
|
90
|
-
rescue IO::WaitWritable
|
91
|
-
if IO.select(nil, [self], nil, @write_timeout)
|
92
|
-
retry
|
93
|
-
else
|
94
|
-
raise Redis::TimeoutError
|
95
|
-
end
|
96
|
-
rescue IO::WaitReadable
|
97
|
-
if IO.select([self], nil, nil, @write_timeout)
|
98
|
-
retry
|
99
|
-
else
|
100
|
-
raise Redis::TimeoutError
|
53
|
+
def _read_from_socket(nbytes, buffer = nil)
|
54
|
+
loop do
|
55
|
+
case chunk = read_nonblock(nbytes, buffer, exception: false)
|
56
|
+
when :wait_readable
|
57
|
+
unless wait_readable(@timeout)
|
58
|
+
raise Redis::TimeoutError
|
59
|
+
end
|
60
|
+
when :wait_writable
|
61
|
+
unless wait_writable(@timeout)
|
62
|
+
raise Redis::TimeoutError
|
63
|
+
end
|
64
|
+
when nil
|
65
|
+
raise Errno::ECONNRESET
|
66
|
+
when String
|
67
|
+
return chunk
|
101
68
|
end
|
102
69
|
end
|
103
|
-
|
104
|
-
rescue EOFError
|
105
|
-
raise Errno::ECONNRESET
|
106
70
|
end
|
107
71
|
|
108
|
-
def write(
|
109
|
-
return super(
|
72
|
+
def write(buffer)
|
73
|
+
return super(buffer) unless @write_timeout
|
110
74
|
|
111
|
-
|
112
|
-
|
75
|
+
bytes_to_write = buffer.bytesize
|
76
|
+
total_bytes_written = 0
|
113
77
|
loop do
|
114
|
-
|
78
|
+
case bytes_written = write_nonblock(buffer, exception: false)
|
79
|
+
when :wait_readable
|
80
|
+
unless wait_readable(@write_timeout)
|
81
|
+
raise Redis::TimeoutError
|
82
|
+
end
|
83
|
+
when :wait_writable
|
84
|
+
unless wait_writable(@write_timeout)
|
85
|
+
raise Redis::TimeoutError
|
86
|
+
end
|
87
|
+
when nil
|
88
|
+
raise Errno::ECONNRESET
|
89
|
+
when Integer
|
90
|
+
total_bytes_written += bytes_written
|
91
|
+
|
92
|
+
if total_bytes_written >= bytes_to_write
|
93
|
+
return total_bytes_written
|
94
|
+
end
|
115
95
|
|
116
|
-
|
117
|
-
|
118
|
-
data = data.byteslice(count..-1)
|
96
|
+
buffer = buffer.byteslice(bytes_written..-1)
|
97
|
+
end
|
119
98
|
end
|
120
99
|
end
|
121
100
|
end
|
@@ -125,7 +104,6 @@ class Redis
|
|
125
104
|
require "timeout"
|
126
105
|
|
127
106
|
class TCPSocket < ::TCPSocket
|
128
|
-
|
129
107
|
include SocketMixin
|
130
108
|
|
131
109
|
def self.connect(host, port, timeout)
|
@@ -141,7 +119,6 @@ class Redis
|
|
141
119
|
if defined?(::UNIXSocket)
|
142
120
|
|
143
121
|
class UNIXSocket < ::UNIXSocket
|
144
|
-
|
145
122
|
include SocketMixin
|
146
123
|
|
147
124
|
def self.connect(path, timeout)
|
@@ -153,13 +130,14 @@ class Redis
|
|
153
130
|
raise TimeoutError
|
154
131
|
end
|
155
132
|
|
156
|
-
# JRuby raises Errno::EAGAIN on #read_nonblock even when
|
133
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
157
134
|
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
158
135
|
# Use the blocking #readpartial method instead.
|
159
136
|
|
160
|
-
def _read_from_socket(nbytes)
|
137
|
+
def _read_from_socket(nbytes, _buffer = nil)
|
138
|
+
# JRuby: Throw away the buffer as we won't need it
|
139
|
+
# but still need to support the max arity of 2
|
161
140
|
readpartial(nbytes)
|
162
|
-
|
163
141
|
rescue EOFError
|
164
142
|
raise Errno::ECONNRESET
|
165
143
|
end
|
@@ -170,19 +148,16 @@ class Redis
|
|
170
148
|
else
|
171
149
|
|
172
150
|
class TCPSocket < ::Socket
|
173
|
-
|
174
151
|
include SocketMixin
|
175
152
|
|
176
|
-
def self.connect_addrinfo(
|
177
|
-
sock = new(::Socket.const_get(
|
178
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
153
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
154
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
155
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
179
156
|
|
180
157
|
begin
|
181
158
|
sock.connect_nonblock(sockaddr)
|
182
159
|
rescue Errno::EINPROGRESS
|
183
|
-
|
184
|
-
raise TimeoutError
|
185
|
-
end
|
160
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
186
161
|
|
187
162
|
begin
|
188
163
|
sock.connect_nonblock(sockaddr)
|
@@ -221,14 +196,13 @@ class Redis
|
|
221
196
|
return connect_addrinfo(ai, port, timeout)
|
222
197
|
rescue SystemCallError
|
223
198
|
# Raise if this was our last attempt.
|
224
|
-
raise if addrinfo.length == i+1
|
199
|
+
raise if addrinfo.length == i + 1
|
225
200
|
end
|
226
201
|
end
|
227
202
|
end
|
228
203
|
end
|
229
204
|
|
230
205
|
class UNIXSocket < ::Socket
|
231
|
-
|
232
206
|
include SocketMixin
|
233
207
|
|
234
208
|
def self.connect(path, timeout)
|
@@ -238,9 +212,7 @@ class Redis
|
|
238
212
|
begin
|
239
213
|
sock.connect_nonblock(sockaddr)
|
240
214
|
rescue Errno::EINPROGRESS
|
241
|
-
|
242
|
-
raise TimeoutError
|
243
|
-
end
|
215
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
244
216
|
|
245
217
|
begin
|
246
218
|
sock.connect_nonblock(sockaddr)
|
@@ -258,12 +230,26 @@ class Redis
|
|
258
230
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
259
231
|
include SocketMixin
|
260
232
|
|
233
|
+
unless method_defined?(:wait_readable)
|
234
|
+
def wait_readable(timeout = nil)
|
235
|
+
to_io.wait_readable(timeout)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
unless method_defined?(:wait_writable)
|
240
|
+
def wait_writable(timeout = nil)
|
241
|
+
to_io.wait_writable(timeout)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
261
245
|
def self.connect(host, port, timeout, ssl_params)
|
262
|
-
#
|
246
|
+
# NOTE: this is using Redis::Connection::TCPSocket
|
263
247
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
264
248
|
|
265
249
|
ctx = OpenSSL::SSL::SSLContext.new
|
266
|
-
|
250
|
+
|
251
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
252
|
+
ctx.set_params(ssl_params || {})
|
267
253
|
|
268
254
|
ssl_sock = new(tcp_sock, ctx)
|
269
255
|
ssl_sock.hostname = host
|
@@ -277,20 +263,23 @@ class Redis
|
|
277
263
|
# Instead, you have to retry.
|
278
264
|
ssl_sock.connect_nonblock
|
279
265
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
280
|
-
if
|
266
|
+
if ssl_sock.wait_readable(timeout)
|
281
267
|
retry
|
282
268
|
else
|
283
269
|
raise TimeoutError
|
284
270
|
end
|
285
271
|
rescue IO::WaitWritable
|
286
|
-
if
|
272
|
+
if ssl_sock.wait_writable(timeout)
|
287
273
|
retry
|
288
274
|
else
|
289
275
|
raise TimeoutError
|
290
276
|
end
|
291
277
|
end
|
292
278
|
|
293
|
-
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
279
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
280
|
+
ctx.respond_to?(:verify_hostname) &&
|
281
|
+
!ctx.verify_hostname
|
282
|
+
)
|
294
283
|
ssl_sock.post_connection_check(host)
|
295
284
|
end
|
296
285
|
|
@@ -302,15 +291,16 @@ class Redis
|
|
302
291
|
class Ruby
|
303
292
|
include Redis::Connection::CommandHelper
|
304
293
|
|
305
|
-
MINUS = "-"
|
306
|
-
PLUS = "+"
|
307
|
-
COLON = ":"
|
308
|
-
DOLLAR = "$"
|
309
|
-
ASTERISK = "*"
|
294
|
+
MINUS = "-"
|
295
|
+
PLUS = "+"
|
296
|
+
COLON = ":"
|
297
|
+
DOLLAR = "$"
|
298
|
+
ASTERISK = "*"
|
310
299
|
|
311
300
|
def self.connect(config)
|
312
301
|
if config[:scheme] == "unix"
|
313
302
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
303
|
+
|
314
304
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
315
305
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
316
306
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -326,7 +316,7 @@ class Redis
|
|
326
316
|
instance
|
327
317
|
end
|
328
318
|
|
329
|
-
if [
|
319
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
330
320
|
def set_tcp_keepalive(keepalive)
|
331
321
|
return unless keepalive.is_a?(Hash)
|
332
322
|
|
@@ -338,14 +328,13 @@ class Redis
|
|
338
328
|
|
339
329
|
def get_tcp_keepalive
|
340
330
|
{
|
341
|
-
:
|
342
|
-
:
|
343
|
-
:
|
331
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
332
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
333
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
344
334
|
}
|
345
335
|
end
|
346
336
|
else
|
347
|
-
def set_tcp_keepalive(keepalive)
|
348
|
-
end
|
337
|
+
def set_tcp_keepalive(keepalive); end
|
349
338
|
|
350
339
|
def get_tcp_keepalive
|
351
340
|
{
|
@@ -354,13 +343,12 @@ class Redis
|
|
354
343
|
end
|
355
344
|
|
356
345
|
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
357
|
-
if [
|
346
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
358
347
|
def set_tcp_nodelay
|
359
348
|
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
360
349
|
end
|
361
350
|
else
|
362
|
-
def set_tcp_nodelay
|
363
|
-
end
|
351
|
+
def set_tcp_nodelay; end
|
364
352
|
end
|
365
353
|
|
366
354
|
def initialize(sock)
|
@@ -368,7 +356,7 @@ class Redis
|
|
368
356
|
end
|
369
357
|
|
370
358
|
def connected?
|
371
|
-
|
359
|
+
!!@sock
|
372
360
|
end
|
373
361
|
|
374
362
|
def disconnect
|
@@ -379,9 +367,7 @@ class Redis
|
|
379
367
|
end
|
380
368
|
|
381
369
|
def timeout=(timeout)
|
382
|
-
if @sock.respond_to?(:timeout=)
|
383
|
-
@sock.timeout = timeout
|
384
|
-
end
|
370
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
385
371
|
end
|
386
372
|
|
387
373
|
def write_timeout=(timeout)
|
@@ -396,9 +382,14 @@ class Redis
|
|
396
382
|
line = @sock.gets
|
397
383
|
reply_type = line.slice!(0, 1)
|
398
384
|
format_reply(reply_type, line)
|
399
|
-
|
400
385
|
rescue Errno::EAGAIN
|
401
386
|
raise TimeoutError
|
387
|
+
rescue OpenSSL::SSL::SSLError => ssl_error
|
388
|
+
if ssl_error.message.match?(/SSL_read: unexpected eof while reading/i)
|
389
|
+
raise EOFError, ssl_error.message
|
390
|
+
else
|
391
|
+
raise
|
392
|
+
end
|
402
393
|
end
|
403
394
|
|
404
395
|
def format_reply(reply_type, line)
|
@@ -408,7 +399,7 @@ class Redis
|
|
408
399
|
when COLON then format_integer_reply(line)
|
409
400
|
when DOLLAR then format_bulk_reply(line)
|
410
401
|
when ASTERISK then format_multi_bulk_reply(line)
|
411
|
-
else raise ProtocolError
|
402
|
+
else raise ProtocolError, reply_type
|
412
403
|
end
|
413
404
|
end
|
414
405
|
|
@@ -427,6 +418,7 @@ class Redis
|
|
427
418
|
def format_bulk_reply(line)
|
428
419
|
bulklen = line.to_i
|
429
420
|
return if bulklen == -1
|
421
|
+
|
430
422
|
reply = encode(@sock.read(bulklen))
|
431
423
|
@sock.read(2) # Discard CRLF.
|
432
424
|
reply
|
@@ -1,10 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
require "redis/connection/registry"
|
4
|
+
require "redis/connection/command_helper"
|
5
|
+
require "redis/errors"
|
6
|
+
|
5
7
|
require "em-synchrony"
|
6
8
|
require "hiredis/reader"
|
7
9
|
|
10
|
+
::Redis.deprecate!(
|
11
|
+
"The redis synchrony driver is deprecated and will be removed in redis-rb 5.0.0. " \
|
12
|
+
"We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
|
13
|
+
)
|
14
|
+
|
8
15
|
class Redis
|
9
16
|
module Connection
|
10
17
|
class RedisClient < EventMachine::Connection
|
@@ -47,9 +54,7 @@ class Redis
|
|
47
54
|
|
48
55
|
def read
|
49
56
|
@req = EventMachine::DefaultDeferrable.new
|
50
|
-
if @timeout > 0
|
51
|
-
@req.timeout(@timeout, :timeout)
|
52
|
-
end
|
57
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
53
58
|
EventMachine::Synchrony.sync @req
|
54
59
|
end
|
55
60
|
|
@@ -106,7 +111,7 @@ class Redis
|
|
106
111
|
end
|
107
112
|
|
108
113
|
def connected?
|
109
|
-
@connection
|
114
|
+
@connection&.connected?
|
110
115
|
end
|
111
116
|
|
112
117
|
def timeout=(timeout)
|
@@ -125,11 +130,12 @@ class Redis
|
|
125
130
|
def read
|
126
131
|
type, payload = @connection.read
|
127
132
|
|
128
|
-
|
133
|
+
case type
|
134
|
+
when :reply
|
129
135
|
payload
|
130
|
-
|
136
|
+
when :error
|
131
137
|
raise payload
|
132
|
-
|
138
|
+
when :timeout
|
133
139
|
raise TimeoutError
|
134
140
|
else
|
135
141
|
raise "Unknown type #{type.inspect}"
|
data/lib/redis/connection.rb
CHANGED