redis 4.1.4 → 4.3.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.
data/lib/redis/client.rb CHANGED
@@ -6,24 +6,31 @@ require "cgi"
6
6
 
7
7
  class Redis
8
8
  class Client
9
-
9
+ # Defaults are also used for converting string keys to symbols.
10
10
  DEFAULTS = {
11
- :url => lambda { ENV["REDIS_URL"] },
12
- :scheme => "redis",
13
- :host => "127.0.0.1",
14
- :port => 6379,
15
- :path => nil,
16
- :timeout => 5.0,
17
- :password => nil,
18
- :db => 0,
19
- :driver => nil,
20
- :id => nil,
21
- :tcp_keepalive => 0,
22
- :reconnect_attempts => 1,
23
- :reconnect_delay => 0,
24
- :reconnect_delay_max => 0.5,
25
- :inherit_socket => false
26
- }
11
+ url: -> { ENV["REDIS_URL"] },
12
+ scheme: "redis",
13
+ host: "127.0.0.1",
14
+ port: 6379,
15
+ path: nil,
16
+ read_timeout: nil,
17
+ write_timeout: nil,
18
+ connect_timeout: nil,
19
+ timeout: 5.0,
20
+ username: nil,
21
+ password: nil,
22
+ db: 0,
23
+ driver: nil,
24
+ id: nil,
25
+ tcp_keepalive: 0,
26
+ reconnect_attempts: 1,
27
+ reconnect_delay: 0,
28
+ reconnect_delay_max: 0.5,
29
+ inherit_socket: false,
30
+ logger: nil,
31
+ sentinels: nil,
32
+ role: nil
33
+ }.freeze
27
34
 
28
35
  attr_reader :options
29
36
 
@@ -55,6 +62,10 @@ class Redis
55
62
  @options[:read_timeout]
56
63
  end
57
64
 
65
+ def username
66
+ @options[:username]
67
+ end
68
+
58
69
  def password
59
70
  @options[:password]
60
71
  end
@@ -89,7 +100,7 @@ class Redis
89
100
  @pending_reads = 0
90
101
 
91
102
  @connector =
92
- if options.include?(:sentinels)
103
+ if !@options[:sentinels].nil?
93
104
  Connector::Sentinel.new(@options)
94
105
  elsif options.include?(:connector) && options[:connector].respond_to?(:new)
95
106
  options.delete(:connector).new(@options)
@@ -104,7 +115,17 @@ class Redis
104
115
  # Don't try to reconnect when the connection is fresh
105
116
  with_reconnect(false) do
106
117
  establish_connection
107
- call [:auth, password] if password
118
+ if password
119
+ if username
120
+ begin
121
+ call [:auth, username, password]
122
+ rescue CommandError # Likely on Redis < 6
123
+ call [:auth, password]
124
+ end
125
+ else
126
+ call [:auth, password]
127
+ end
128
+ end
108
129
  call [:select, db] if db != 0
109
130
  call [:client, :setname, @options[:id]] if @options[:id]
110
131
  @connector.check(self)
@@ -125,7 +146,7 @@ class Redis
125
146
  reply = process([command]) { read }
126
147
  raise reply if reply.is_a?(CommandError)
127
148
 
128
- if block_given?
149
+ if block_given? && reply != 'QUEUED'
129
150
  yield reply
130
151
  else
131
152
  reply
@@ -166,6 +187,7 @@ class Redis
166
187
  end
167
188
  rescue ConnectionError => e
168
189
  return nil if pipeline.shutdown?
190
+
169
191
  # Assume the pipeline was sent in one piece, but execution of
170
192
  # SHUTDOWN caused none of the replies for commands that were executed
171
193
  # prior to it from coming back around.
@@ -244,13 +266,13 @@ class Redis
244
266
  end
245
267
 
246
268
  def connected?
247
- !! (connection && connection.connected?)
269
+ !!(connection && connection.connected?)
248
270
  end
249
271
 
250
272
  def disconnect
251
273
  connection.disconnect if connected?
252
274
  end
253
- alias_method :close, :disconnect
275
+ alias close disconnect
254
276
 
255
277
  def reconnect
256
278
  disconnect
@@ -301,30 +323,27 @@ class Redis
301
323
  with_socket_timeout(0, &blk)
302
324
  end
303
325
 
304
- def with_reconnect(val=true)
305
- begin
306
- original, @reconnect = @reconnect, val
307
- yield
308
- ensure
309
- @reconnect = original
310
- end
326
+ def with_reconnect(val = true)
327
+ original, @reconnect = @reconnect, val
328
+ yield
329
+ ensure
330
+ @reconnect = original
311
331
  end
312
332
 
313
333
  def without_reconnect(&blk)
314
334
  with_reconnect(false, &blk)
315
335
  end
316
336
 
317
- protected
337
+ protected
318
338
 
319
339
  def logging(commands)
320
- return yield unless @logger && @logger.debug?
340
+ return yield unless @logger&.debug?
321
341
 
322
342
  begin
323
343
  commands.each do |name, *args|
324
344
  logged_args = args.map do |a|
325
- case
326
- when a.respond_to?(:inspect) then a.inspect
327
- when a.respond_to?(:to_s) then a.to_s
345
+ if a.respond_to?(:inspect) then a.inspect
346
+ elsif a.respond_to?(:to_s) then a.to_s
328
347
  else
329
348
  # handle poorly-behaved descendants of BasicObject
330
349
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -358,9 +377,9 @@ class Redis
358
377
  Errno::ENETUNREACH,
359
378
  Errno::ENOENT,
360
379
  Errno::ETIMEDOUT,
361
- Errno::EINVAL
380
+ Errno::EINVAL => error
362
381
 
363
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
382
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
364
383
  end
365
384
 
366
385
  def ensure_connected
@@ -374,9 +393,9 @@ class Redis
374
393
  if connected?
375
394
  unless inherit_socket? || Process.pid == @pid
376
395
  raise InheritedError,
377
- "Tried to use a connection from a child process without reconnecting. " +
378
- "You need to reconnect to Redis after forking " +
379
- "or set :inherit_socket to true."
396
+ "Tried to use a connection from a child process without reconnecting. " \
397
+ "You need to reconnect to Redis after forking " \
398
+ "or set :inherit_socket to true."
380
399
  end
381
400
  else
382
401
  connect
@@ -387,7 +406,7 @@ class Redis
387
406
  disconnect
388
407
 
389
408
  if attempts <= @options[:reconnect_attempts] && @reconnect
390
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
409
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
391
410
  @options[:reconnect_delay_max]].min
392
411
 
393
412
  Kernel.sleep(sleep_t)
@@ -409,16 +428,14 @@ class Redis
409
428
 
410
429
  defaults.keys.each do |key|
411
430
  # Fill in defaults if needed
412
- if defaults[key].respond_to?(:call)
413
- defaults[key] = defaults[key].call
414
- end
431
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
415
432
 
416
433
  # Symbolize only keys that are needed
417
- options[key] = options[key.to_s] if options.has_key?(key.to_s)
434
+ options[key] = options[key.to_s] if options.key?(key.to_s)
418
435
  end
419
436
 
420
437
  url = options[:url]
421
- url = defaults[:url] if url == nil
438
+ url = defaults[:url] if url.nil?
422
439
 
423
440
  # Override defaults from URL if given
424
441
  if url
@@ -427,12 +444,13 @@ class Redis
427
444
  uri = URI(url)
428
445
 
429
446
  if uri.scheme == "unix"
430
- defaults[:path] = uri.path
447
+ defaults[:path] = uri.path
431
448
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
432
449
  defaults[:scheme] = uri.scheme
433
450
  defaults[:host] = uri.host if uri.host
434
451
  defaults[:port] = uri.port if uri.port
435
- defaults[:password] = CGI.unescape(uri.password) if uri.password
452
+ defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
453
+ defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
436
454
  defaults[:db] = uri.path[1..-1].to_i if uri.path
437
455
  defaults[:role] = :master
438
456
  else
@@ -458,7 +476,7 @@ class Redis
458
476
  options[:port] = options[:port].to_i
459
477
  end
460
478
 
461
- if options.has_key?(:timeout)
479
+ if options.key?(:timeout)
462
480
  options[:connect_timeout] ||= options[:timeout]
463
481
  options[:read_timeout] ||= options[:timeout]
464
482
  options[:write_timeout] ||= options[:timeout]
@@ -477,7 +495,7 @@ class Redis
477
495
 
478
496
  case options[:tcp_keepalive]
479
497
  when Hash
480
- [:time, :intvl, :probes].each do |key|
498
+ %i[time intvl probes].each do |key|
481
499
  unless options[:tcp_keepalive][key].is_a?(Integer)
482
500
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
483
501
  end
@@ -485,13 +503,13 @@ class Redis
485
503
 
486
504
  when Integer
487
505
  if options[:tcp_keepalive] >= 60
488
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
506
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
489
507
 
490
508
  elsif options[:tcp_keepalive] >= 30
491
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
509
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
492
510
 
493
511
  elsif options[:tcp_keepalive] >= 5
494
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
512
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
495
513
  end
496
514
  end
497
515
 
@@ -503,14 +521,14 @@ class Redis
503
521
  def _parse_driver(driver)
504
522
  driver = driver.to_s if driver.is_a?(Symbol)
505
523
 
506
- if driver.kind_of?(String)
524
+ if driver.is_a?(String)
507
525
  begin
508
526
  require_relative "connection/#{driver}"
509
- rescue LoadError, NameError => e
527
+ rescue LoadError, NameError
510
528
  begin
511
- require "connection/#{driver}"
512
- rescue LoadError, NameError => e
513
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
529
+ require "redis/connection/#{driver}"
530
+ rescue LoadError, NameError => error
531
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
514
532
  end
515
533
  end
516
534
 
@@ -529,8 +547,7 @@ class Redis
529
547
  @options
530
548
  end
531
549
 
532
- def check(client)
533
- end
550
+ def check(client); end
534
551
 
535
552
  class Sentinel < Connector
536
553
  def initialize(options)
@@ -539,7 +556,7 @@ class Redis
539
556
  @options[:db] = DEFAULTS.fetch(:db)
540
557
 
541
558
  @sentinels = @options.delete(:sentinels).dup
542
- @role = @options.fetch(:role, "master").to_s
559
+ @role = (@options[:role] || "master").to_s
543
560
  @master = @options[:host]
544
561
  end
545
562
 
@@ -562,13 +579,13 @@ class Redis
562
579
 
563
580
  def resolve
564
581
  result = case @role
565
- when "master"
566
- resolve_master
567
- when "slave"
568
- resolve_slave
569
- else
570
- raise ArgumentError, "Unknown instance role #{@role}"
571
- end
582
+ when "master"
583
+ resolve_master
584
+ when "slave"
585
+ resolve_slave
586
+ else
587
+ raise ArgumentError, "Unknown instance role #{@role}"
588
+ end
572
589
 
573
590
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
574
591
  end
@@ -576,11 +593,12 @@ class Redis
576
593
  def sentinel_detect
577
594
  @sentinels.each do |sentinel|
578
595
  client = Client.new(@options.merge({
579
- :host => sentinel[:host],
580
- :port => sentinel[:port],
581
- password: sentinel[:password],
582
- :reconnect_attempts => 0,
583
- }))
596
+ host: sentinel[:host] || sentinel["host"],
597
+ port: sentinel[:port] || sentinel["port"],
598
+ username: sentinel[:username] || sentinel["username"],
599
+ password: sentinel[:password] || sentinel["password"],
600
+ reconnect_attempts: 0
601
+ }))
584
602
 
585
603
  begin
586
604
  if result = yield(client)
@@ -602,7 +620,7 @@ class Redis
602
620
  def resolve_master
603
621
  sentinel_detect do |client|
604
622
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
605
- {:host => reply[0], :port => reply[1]}
623
+ { host: reply[0], port: reply[1] }
606
624
  end
607
625
  end
608
626
  end
@@ -620,7 +638,7 @@ class Redis
620
638
  slave = slaves.sample
621
639
  {
622
640
  host: slave.fetch('ip'),
623
- port: slave.fetch('port'),
641
+ port: slave.fetch('port')
624
642
  }
625
643
  end
626
644
  end
data/lib/redis/cluster.rb CHANGED
@@ -80,6 +80,7 @@ class Redis
80
80
  def call_pipeline(pipeline)
81
81
  node_keys, command_keys = extract_keys_in_pipeline(pipeline)
82
82
  raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
83
+
83
84
  node = find_node(node_keys.first)
84
85
  try_send(node, :call_pipeline, pipeline)
85
86
  end
@@ -127,7 +128,7 @@ class Redis
127
128
  def send_command(command, &block)
128
129
  cmd = command.first.to_s.downcase
129
130
  case cmd
130
- when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
131
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
131
132
  @node.call_all(command, &block).first
132
133
  when 'flushall', 'flushdb'
133
134
  @node.call_master(command, &block).first
@@ -216,11 +217,13 @@ class Redis
216
217
  rescue CommandError => err
217
218
  if err.message.start_with?('MOVED')
218
219
  raise if retry_count <= 0
220
+
219
221
  node = assign_redirection_node(err.message)
220
222
  retry_count -= 1
221
223
  retry
222
224
  elsif err.message.start_with?('ASK')
223
225
  raise if retry_count <= 0
226
+
224
227
  node = assign_asking_node(err.message)
225
228
  node.call(%i[asking])
226
229
  retry_count -= 1
@@ -266,6 +269,7 @@ class Redis
266
269
 
267
270
  def find_node(node_key)
268
271
  return @node.sample if node_key.nil?
272
+
269
273
  @node.find_by(node_key)
270
274
  rescue Node::ReloadNeeded
271
275
  update_cluster_info!(node_key)
@@ -39,6 +39,7 @@ class Redis
39
39
  def call_master(command, &block)
40
40
  try_map do |node_key, client|
41
41
  next if slave?(node_key)
42
+
42
43
  client.call(command, &block)
43
44
  end.values
44
45
  end
@@ -48,6 +49,7 @@ class Redis
48
49
 
49
50
  try_map do |node_key, client|
50
51
  next if master?(node_key)
52
+
51
53
  client.call(command, &block)
52
54
  end.values
53
55
  end
@@ -97,6 +99,7 @@ class Redis
97
99
  end
98
100
 
99
101
  return results if errors.empty?
102
+
100
103
  raise CommandErrorCollection, errors
101
104
  end
102
105
  end
@@ -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