redis 4.1.4 → 4.4.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.
@@ -18,6 +18,7 @@ class Redis
18
18
  @node_opts = build_node_options(node_addrs)
19
19
  @replica = options.delete(:replica) == true
20
20
  add_common_node_option_if_needed(options, @node_opts, :scheme)
21
+ add_common_node_option_if_needed(options, @node_opts, :username)
21
22
  add_common_node_option_if_needed(options, @node_opts, :password)
22
23
  @options = options
23
24
  end
@@ -43,6 +44,7 @@ class Redis
43
44
 
44
45
  def build_node_options(addrs)
45
46
  raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
47
+
46
48
  addrs.map { |addr| parse_node_addr(addr) }
47
49
  end
48
50
 
@@ -62,21 +64,25 @@ class Redis
62
64
  raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
63
65
 
64
66
  db = uri.path.split('/')[1]&.to_i
65
- { scheme: uri.scheme, password: uri.password, host: uri.host, port: uri.port, db: db }.reject { |_, v| v.nil? }
67
+
68
+ { scheme: uri.scheme, username: uri.user, password: uri.password, host: uri.host, port: uri.port, db: db }
69
+ .reject { |_, v| v.nil? || v == '' }
66
70
  rescue URI::InvalidURIError => err
67
71
  raise InvalidClientOptionError, err.message
68
72
  end
69
73
 
70
74
  def parse_node_option(addr)
71
75
  addr = addr.map { |k, v| [k.to_sym, v] }.to_h
72
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?)
76
+ if addr.values_at(:host, :port).any?(&:nil?)
77
+ raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
78
+ end
73
79
 
74
80
  addr
75
81
  end
76
82
 
77
83
  # Redis cluster node returns only host and port information.
78
84
  # So we should complement additional information such as:
79
- # scheme, password and so on.
85
+ # scheme, username, password and so on.
80
86
  def add_common_node_option_if_needed(options, node_opts, key)
81
87
  return options if options[key].nil? && node_opts.first[key].nil?
82
88
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
-
5
3
  class Redis
6
4
  class Cluster
7
5
  # Keep slot and node key map for Redis Cluster Client
@@ -28,11 +26,20 @@ class Redis
28
26
  return nil unless exists?(slot)
29
27
  return find_node_key_of_master(slot) if replica_disabled?
30
28
 
31
- @map[slot][:slaves].to_a.sample
29
+ @map[slot][:slaves].sample
32
30
  end
33
31
 
34
32
  def put(slot, node_key)
35
- assign_node_key(@map, slot, node_key)
33
+ # Since we're sharing a hash for build_slot_node_key_map, duplicate it
34
+ # if it already exists instead of preserving as-is.
35
+ @map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
36
+
37
+ if master?(node_key)
38
+ @map[slot][:master] = node_key
39
+ elsif !@map[slot][:slaves].include?(node_key)
40
+ @map[slot][:slaves] << node_key
41
+ end
42
+
36
43
  nil
37
44
  end
38
45
 
@@ -52,20 +59,27 @@ class Redis
52
59
 
53
60
  # available_slots is mapping of node_key to list of slot ranges
54
61
  def build_slot_node_key_map(available_slots)
55
- available_slots.each_with_object({}) do |(node_key, slots_arr), acc|
56
- slots_arr.each do |slots|
57
- slots.each { |slot| assign_node_key(acc, slot, node_key) }
62
+ by_ranges = {}
63
+ available_slots.each do |node_key, slots_arr|
64
+ by_ranges[slots_arr] ||= { master: nil, slaves: [] }
65
+
66
+ if master?(node_key)
67
+ by_ranges[slots_arr][:master] = node_key
68
+ elsif !by_ranges[slots_arr][:slaves].include?(node_key)
69
+ by_ranges[slots_arr][:slaves] << node_key
58
70
  end
59
71
  end
60
- end
61
72
 
62
- def assign_node_key(mappings, slot, node_key)
63
- mappings[slot] ||= { master: nil, slaves: ::Set.new }
64
- if master?(node_key)
65
- mappings[slot][:master] = node_key
66
- else
67
- mappings[slot][:slaves].add(node_key)
73
+ by_slot = {}
74
+ by_ranges.each do |slots_arr, nodes|
75
+ slots_arr.each do |slots|
76
+ slots.each do |slot|
77
+ by_slot[slot] = nodes
78
+ end
79
+ end
68
80
  end
81
+
82
+ by_slot
69
83
  end
70
84
  end
71
85
  end
@@ -25,9 +25,8 @@ class Redis
25
25
  def fetch_slot_info(node)
26
26
  hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
27
27
  node.call(%i[cluster slots])
28
- .flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
29
- .each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
30
-
28
+ .flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
29
+ .each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
31
30
  rescue CannotConnectError, ConnectionError, CommandError
32
31
  {} # can retry on another node
33
32
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative "connection/registry"
3
4
 
4
5
  # If a connection driver was required before this file, the array
@@ -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
- protected
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 && @connection.connected?
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.new(err.message)
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,97 +25,74 @@ 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
- 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
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
101
66
  end
102
67
  end
103
-
104
- rescue EOFError
105
- raise Errno::ECONNRESET
106
68
  end
107
69
 
108
- def write(data)
109
- return super(data) unless @write_timeout
70
+ def write(buffer)
71
+ return super(buffer) unless @write_timeout
110
72
 
111
- length = data.bytesize
112
- total_count = 0
73
+ bytes_to_write = buffer.bytesize
74
+ total_bytes_written = 0
113
75
  loop do
114
- count = _write_to_socket(data)
76
+ case bytes_written = write_nonblock(buffer, exception: false)
77
+ when :wait_readable
78
+ unless wait_readable(@write_timeout)
79
+ raise Redis::TimeoutError
80
+ end
81
+ when :wait_writable
82
+ unless wait_writable(@write_timeout)
83
+ raise Redis::TimeoutError
84
+ end
85
+ when nil
86
+ raise Errno::ECONNRESET
87
+ when Integer
88
+ total_bytes_written += bytes_written
115
89
 
116
- total_count += count
117
- return total_count if total_count >= length
118
- data = data.byteslice(count..-1)
90
+ if total_bytes_written >= bytes_to_write
91
+ return total_bytes_written
92
+ end
93
+
94
+ buffer = buffer.byteslice(bytes_written..-1)
95
+ end
119
96
  end
120
97
  end
121
98
  end
@@ -125,7 +102,6 @@ class Redis
125
102
  require "timeout"
126
103
 
127
104
  class TCPSocket < ::TCPSocket
128
-
129
105
  include SocketMixin
130
106
 
131
107
  def self.connect(host, port, timeout)
@@ -141,7 +117,6 @@ class Redis
141
117
  if defined?(::UNIXSocket)
142
118
 
143
119
  class UNIXSocket < ::UNIXSocket
144
-
145
120
  include SocketMixin
146
121
 
147
122
  def self.connect(path, timeout)
@@ -153,13 +128,12 @@ class Redis
153
128
  raise TimeoutError
154
129
  end
155
130
 
156
- # JRuby raises Errno::EAGAIN on #read_nonblock even when IO.select
131
+ # JRuby raises Errno::EAGAIN on #read_nonblock even when it
157
132
  # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
158
133
  # Use the blocking #readpartial method instead.
159
134
 
160
135
  def _read_from_socket(nbytes)
161
136
  readpartial(nbytes)
162
-
163
137
  rescue EOFError
164
138
  raise Errno::ECONNRESET
165
139
  end
@@ -170,19 +144,16 @@ class Redis
170
144
  else
171
145
 
172
146
  class TCPSocket < ::Socket
173
-
174
147
  include SocketMixin
175
148
 
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])
149
+ def self.connect_addrinfo(addrinfo, port, timeout)
150
+ sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
151
+ sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
179
152
 
180
153
  begin
181
154
  sock.connect_nonblock(sockaddr)
182
155
  rescue Errno::EINPROGRESS
183
- if IO.select(nil, [sock], nil, timeout) == nil
184
- raise TimeoutError
185
- end
156
+ raise TimeoutError unless sock.wait_writable(timeout)
186
157
 
187
158
  begin
188
159
  sock.connect_nonblock(sockaddr)
@@ -221,14 +192,13 @@ class Redis
221
192
  return connect_addrinfo(ai, port, timeout)
222
193
  rescue SystemCallError
223
194
  # Raise if this was our last attempt.
224
- raise if addrinfo.length == i+1
195
+ raise if addrinfo.length == i + 1
225
196
  end
226
197
  end
227
198
  end
228
199
  end
229
200
 
230
201
  class UNIXSocket < ::Socket
231
-
232
202
  include SocketMixin
233
203
 
234
204
  def self.connect(path, timeout)
@@ -238,9 +208,7 @@ class Redis
238
208
  begin
239
209
  sock.connect_nonblock(sockaddr)
240
210
  rescue Errno::EINPROGRESS
241
- if IO.select(nil, [sock], nil, timeout) == nil
242
- raise TimeoutError
243
- end
211
+ raise TimeoutError unless sock.wait_writable(timeout)
244
212
 
245
213
  begin
246
214
  sock.connect_nonblock(sockaddr)
@@ -258,12 +226,26 @@ class Redis
258
226
  class SSLSocket < ::OpenSSL::SSL::SSLSocket
259
227
  include SocketMixin
260
228
 
229
+ unless method_defined?(:wait_readable)
230
+ def wait_readable(timeout = nil)
231
+ to_io.wait_readable(timeout)
232
+ end
233
+ end
234
+
235
+ unless method_defined?(:wait_writable)
236
+ def wait_writable(timeout = nil)
237
+ to_io.wait_writable(timeout)
238
+ end
239
+ end
240
+
261
241
  def self.connect(host, port, timeout, ssl_params)
262
242
  # Note: this is using Redis::Connection::TCPSocket
263
243
  tcp_sock = TCPSocket.connect(host, port, timeout)
264
244
 
265
245
  ctx = OpenSSL::SSL::SSLContext.new
266
- ctx.set_params(ssl_params) if ssl_params && !ssl_params.empty?
246
+
247
+ # The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
248
+ ctx.set_params(ssl_params || {})
267
249
 
268
250
  ssl_sock = new(tcp_sock, ctx)
269
251
  ssl_sock.hostname = host
@@ -277,20 +259,23 @@ class Redis
277
259
  # Instead, you have to retry.
278
260
  ssl_sock.connect_nonblock
279
261
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
280
- if IO.select([ssl_sock], nil, nil, timeout)
262
+ if ssl_sock.wait_readable(timeout)
281
263
  retry
282
264
  else
283
265
  raise TimeoutError
284
266
  end
285
267
  rescue IO::WaitWritable
286
- if IO.select(nil, [ssl_sock], nil, timeout)
268
+ if ssl_sock.wait_writable(timeout)
287
269
  retry
288
270
  else
289
271
  raise TimeoutError
290
272
  end
291
273
  end
292
274
 
293
- unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (ctx.respond_to?(:verify_hostname) && !ctx.verify_hostname)
275
+ unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
276
+ ctx.respond_to?(:verify_hostname) &&
277
+ !ctx.verify_hostname
278
+ )
294
279
  ssl_sock.post_connection_check(host)
295
280
  end
296
281
 
@@ -302,15 +287,16 @@ class Redis
302
287
  class Ruby
303
288
  include Redis::Connection::CommandHelper
304
289
 
305
- MINUS = "-".freeze
306
- PLUS = "+".freeze
307
- COLON = ":".freeze
308
- DOLLAR = "$".freeze
309
- ASTERISK = "*".freeze
290
+ MINUS = "-"
291
+ PLUS = "+"
292
+ COLON = ":"
293
+ DOLLAR = "$"
294
+ ASTERISK = "*"
310
295
 
311
296
  def self.connect(config)
312
297
  if config[:scheme] == "unix"
313
298
  raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
299
+
314
300
  sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
315
301
  elsif config[:scheme] == "rediss" || config[:ssl]
316
302
  sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
@@ -326,7 +312,7 @@ class Redis
326
312
  instance
327
313
  end
328
314
 
329
- if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
315
+ if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
330
316
  def set_tcp_keepalive(keepalive)
331
317
  return unless keepalive.is_a?(Hash)
332
318
 
@@ -338,14 +324,13 @@ class Redis
338
324
 
339
325
  def get_tcp_keepalive
340
326
  {
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,
327
+ time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
328
+ intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
329
+ probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
344
330
  }
345
331
  end
346
332
  else
347
- def set_tcp_keepalive(keepalive)
348
- end
333
+ def set_tcp_keepalive(keepalive); end
349
334
 
350
335
  def get_tcp_keepalive
351
336
  {
@@ -354,13 +339,12 @@ class Redis
354
339
  end
355
340
 
356
341
  # disables Nagle's Algorithm, prevents multiple round trips with MULTI
357
- if [:IPPROTO_TCP, :TCP_NODELAY].all?{|c| Socket.const_defined? c}
342
+ if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
358
343
  def set_tcp_nodelay
359
344
  @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
360
345
  end
361
346
  else
362
- def set_tcp_nodelay
363
- end
347
+ def set_tcp_nodelay; end
364
348
  end
365
349
 
366
350
  def initialize(sock)
@@ -368,7 +352,7 @@ class Redis
368
352
  end
369
353
 
370
354
  def connected?
371
- !! @sock
355
+ !!@sock
372
356
  end
373
357
 
374
358
  def disconnect
@@ -379,9 +363,7 @@ class Redis
379
363
  end
380
364
 
381
365
  def timeout=(timeout)
382
- if @sock.respond_to?(:timeout=)
383
- @sock.timeout = timeout
384
- end
366
+ @sock.timeout = timeout if @sock.respond_to?(:timeout=)
385
367
  end
386
368
 
387
369
  def write_timeout=(timeout)
@@ -396,7 +378,6 @@ class Redis
396
378
  line = @sock.gets
397
379
  reply_type = line.slice!(0, 1)
398
380
  format_reply(reply_type, line)
399
-
400
381
  rescue Errno::EAGAIN
401
382
  raise TimeoutError
402
383
  end
@@ -408,7 +389,7 @@ class Redis
408
389
  when COLON then format_integer_reply(line)
409
390
  when DOLLAR then format_bulk_reply(line)
410
391
  when ASTERISK then format_multi_bulk_reply(line)
411
- else raise ProtocolError.new(reply_type)
392
+ else raise ProtocolError, reply_type
412
393
  end
413
394
  end
414
395
 
@@ -427,6 +408,7 @@ class Redis
427
408
  def format_bulk_reply(line)
428
409
  bulklen = line.to_i
429
410
  return if bulklen == -1
411
+
430
412
  reply = encode(@sock.read(bulklen))
431
413
  @sock.read(2) # Discard CRLF.
432
414
  reply