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