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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +158 -0
  3. data/README.md +91 -27
  4. data/lib/redis/client.rb +148 -92
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +6 -7
  7. data/lib/redis/cluster/node.rb +17 -1
  8. data/lib/redis/cluster/node_key.rb +3 -7
  9. data/lib/redis/cluster/option.rb +30 -14
  10. data/lib/redis/cluster/slot.rb +30 -13
  11. data/lib/redis/cluster/slot_loader.rb +4 -4
  12. data/lib/redis/cluster.rb +46 -17
  13. data/lib/redis/commands/bitmaps.rb +63 -0
  14. data/lib/redis/commands/cluster.rb +45 -0
  15. data/lib/redis/commands/connection.rb +58 -0
  16. data/lib/redis/commands/geo.rb +84 -0
  17. data/lib/redis/commands/hashes.rb +251 -0
  18. data/lib/redis/commands/hyper_log_log.rb +37 -0
  19. data/lib/redis/commands/keys.rb +411 -0
  20. data/lib/redis/commands/lists.rb +289 -0
  21. data/lib/redis/commands/pubsub.rb +72 -0
  22. data/lib/redis/commands/scripting.rb +114 -0
  23. data/lib/redis/commands/server.rb +188 -0
  24. data/lib/redis/commands/sets.rb +207 -0
  25. data/lib/redis/commands/sorted_sets.rb +804 -0
  26. data/lib/redis/commands/streams.rb +382 -0
  27. data/lib/redis/commands/strings.rb +313 -0
  28. data/lib/redis/commands/transactions.rb +92 -0
  29. data/lib/redis/commands.rb +242 -0
  30. data/lib/redis/connection/command_helper.rb +5 -2
  31. data/lib/redis/connection/hiredis.rb +7 -5
  32. data/lib/redis/connection/registry.rb +2 -1
  33. data/lib/redis/connection/ruby.rb +129 -110
  34. data/lib/redis/connection/synchrony.rb +17 -10
  35. data/lib/redis/connection.rb +3 -1
  36. data/lib/redis/distributed.rb +209 -70
  37. data/lib/redis/errors.rb +2 -0
  38. data/lib/redis/hash_ring.rb +15 -14
  39. data/lib/redis/pipeline.rb +139 -8
  40. data/lib/redis/subscribe.rb +11 -12
  41. data/lib/redis/version.rb +3 -1
  42. data/lib/redis.rb +167 -3377
  43. metadata +32 -25
@@ -1,6 +1,9 @@
1
- require_relative "registry"
2
- require_relative "command_helper"
3
- require_relative "../errors"
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 = "".dup
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
- while result.bytesize < nbytes
46
- result << _read_from_socket(nbytes - result.bytesize)
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
- begin
65
- read_nonblock(nbytes)
66
-
67
- rescue IO::WaitReadable
68
- if IO.select([self], nil, nil, @timeout)
69
- retry
70
- else
71
- raise Redis::TimeoutError
72
- end
73
- rescue IO::WaitWritable
74
- if IO.select(nil, [self], nil, @timeout)
75
- retry
76
- else
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(data)
108
- return super(data) unless @write_timeout
72
+ def write(buffer)
73
+ return super(buffer) unless @write_timeout
109
74
 
110
- length = data.bytesize
111
- total_count = 0
75
+ bytes_to_write = buffer.bytesize
76
+ total_bytes_written = 0
112
77
  loop do
113
- count = _write_to_socket(data)
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
- total_count += count
116
- return total_count if total_count >= length
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 IO.select
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(ai, port, timeout)
176
- sock = new(::Socket.const_get(ai[0]), Socket::SOCK_STREAM, 0)
177
- sockaddr = ::Socket.pack_sockaddr_in(port, ai[3])
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
- if IO.select(nil, [sock], nil, timeout) == nil
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
- if IO.select(nil, [sock], nil, timeout) == nil
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
- # Note: this is using Redis::Connection::TCPSocket
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
- ctx.set_params(ssl_params) if ssl_params && !ssl_params.empty?
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
- unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
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 = "-".freeze
284
- PLUS = "+".freeze
285
- COLON = ":".freeze
286
- DOLLAR = "$".freeze
287
- ASTERISK = "*".freeze
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 [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
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
- :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
319
- :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
320
- :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int,
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
- !! @sock
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.new(reply_type)
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
- require_relative "command_helper"
2
- require_relative "registry"
3
- require_relative "../errors"
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 && @connection.connected?
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
- if type == :reply
133
+ case type
134
+ when :reply
128
135
  payload
129
- elsif type == :error
136
+ when :error
130
137
  raise payload
131
- elsif type == :timeout
138
+ when :timeout
132
139
  raise TimeoutError
133
140
  else
134
141
  raise "Unknown type #{type.inspect}"
@@ -1,4 +1,6 @@
1
- require_relative "connection/registry"
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/connection/registry"
2
4
 
3
5
  # If a connection driver was required before this file, the array
4
6
  # Redis::Connection.drivers will contain one or more classes. The last driver