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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +141 -0
  3. data/README.md +52 -27
  4. data/lib/redis/client.rb +122 -87
  5. data/lib/redis/cluster/command.rb +4 -6
  6. data/lib/redis/cluster/command_loader.rb +8 -9
  7. data/lib/redis/cluster/node.rb +17 -1
  8. data/lib/redis/cluster/node_loader.rb +8 -11
  9. data/lib/redis/cluster/option.rb +18 -5
  10. data/lib/redis/cluster/slot.rb +28 -14
  11. data/lib/redis/cluster/slot_loader.rb +11 -15
  12. data/lib/redis/cluster.rb +37 -13
  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 +812 -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 +139 -0
  29. data/lib/redis/commands.rb +242 -0
  30. data/lib/redis/connection/command_helper.rb +4 -2
  31. data/lib/redis/connection/hiredis.rb +6 -7
  32. data/lib/redis/connection/registry.rb +1 -1
  33. data/lib/redis/connection/ruby.rb +106 -114
  34. data/lib/redis/connection/synchrony.rb +16 -10
  35. data/lib/redis/connection.rb +2 -1
  36. data/lib/redis/distributed.rb +200 -65
  37. data/lib/redis/errors.rb +10 -0
  38. data/lib/redis/hash_ring.rb +14 -14
  39. data/lib/redis/pipeline.rb +133 -10
  40. data/lib/redis/subscribe.rb +10 -12
  41. data/lib/redis/version.rb +2 -1
  42. data/lib/redis.rb +158 -3358
  43. metadata +32 -10
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require_relative "registry"
3
- require_relative "command_helper"
4
- require_relative "../errors"
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 = "".dup
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
- while result.bytesize < nbytes
47
- result << _read_from_socket(nbytes - result.bytesize)
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
- begin
66
- read_nonblock(nbytes)
67
-
68
- rescue IO::WaitReadable
69
- if IO.select([self], nil, nil, @timeout)
70
- retry
71
- else
72
- raise Redis::TimeoutError
73
- end
74
- rescue IO::WaitWritable
75
- if IO.select(nil, [self], nil, @timeout)
76
- retry
77
- else
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(data)
109
- return super(data) unless @write_timeout
72
+ def write(buffer)
73
+ return super(buffer) unless @write_timeout
110
74
 
111
- length = data.bytesize
112
- total_count = 0
75
+ bytes_to_write = buffer.bytesize
76
+ total_bytes_written = 0
113
77
  loop do
114
- 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
115
95
 
116
- total_count += count
117
- return total_count if total_count >= length
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 IO.select
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(ai, port, timeout)
177
- sock = new(::Socket.const_get(ai[0]), Socket::SOCK_STREAM, 0)
178
- 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])
179
156
 
180
157
  begin
181
158
  sock.connect_nonblock(sockaddr)
182
159
  rescue Errno::EINPROGRESS
183
- if IO.select(nil, [sock], nil, timeout) == nil
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
- if IO.select(nil, [sock], nil, timeout) == nil
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
- # Note: this is using Redis::Connection::TCPSocket
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
- 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 || {})
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 IO.select([ssl_sock], nil, nil, timeout)
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 IO.select(nil, [ssl_sock], nil, timeout)
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 || (ctx.respond_to?(:verify_hostname) && !ctx.verify_hostname)
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 = "-".freeze
306
- PLUS = "+".freeze
307
- COLON = ":".freeze
308
- DOLLAR = "$".freeze
309
- ASTERISK = "*".freeze
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 [: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 }
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
- :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
342
- :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
343
- :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
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 [:IPPROTO_TCP, :TCP_NODELAY].all?{|c| Socket.const_defined? c}
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
- !! @sock
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.new(reply_type)
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
- require_relative "command_helper"
3
- require_relative "registry"
4
- require_relative "../errors"
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 && @connection.connected?
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
- if type == :reply
133
+ case type
134
+ when :reply
129
135
  payload
130
- elsif type == :error
136
+ when :error
131
137
  raise payload
132
- elsif type == :timeout
138
+ when :timeout
133
139
  raise TimeoutError
134
140
  else
135
141
  raise "Unknown type #{type.inspect}"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require_relative "connection/registry"
2
+
3
+ require "redis/connection/registry"
3
4
 
4
5
  # If a connection driver was required before this file, the array
5
6
  # Redis::Connection.drivers will contain one or more classes. The last driver