redis 4.1.4 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
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