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.
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,19 @@ 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
129
+
130
+ call [:readonly] if @options[:readonly]
108
131
  call [:select, db] if db != 0
109
132
  call [:client, :setname, @options[:id]] if @options[:id]
110
133
  @connector.check(self)
@@ -125,7 +148,7 @@ class Redis
125
148
  reply = process([command]) { read }
126
149
  raise reply if reply.is_a?(CommandError)
127
150
 
128
- if block_given?
151
+ if block_given? && reply != 'QUEUED'
129
152
  yield reply
130
153
  else
131
154
  reply
@@ -166,6 +189,7 @@ class Redis
166
189
  end
167
190
  rescue ConnectionError => e
168
191
  return nil if pipeline.shutdown?
192
+
169
193
  # Assume the pipeline was sent in one piece, but execution of
170
194
  # SHUTDOWN caused none of the replies for commands that were executed
171
195
  # prior to it from coming back around.
@@ -244,13 +268,13 @@ class Redis
244
268
  end
245
269
 
246
270
  def connected?
247
- !! (connection && connection.connected?)
271
+ !!(connection && connection.connected?)
248
272
  end
249
273
 
250
274
  def disconnect
251
275
  connection.disconnect if connected?
252
276
  end
253
- alias_method :close, :disconnect
277
+ alias close disconnect
254
278
 
255
279
  def reconnect
256
280
  disconnect
@@ -301,30 +325,27 @@ class Redis
301
325
  with_socket_timeout(0, &blk)
302
326
  end
303
327
 
304
- def with_reconnect(val=true)
305
- begin
306
- original, @reconnect = @reconnect, val
307
- yield
308
- ensure
309
- @reconnect = original
310
- end
328
+ def with_reconnect(val = true)
329
+ original, @reconnect = @reconnect, val
330
+ yield
331
+ ensure
332
+ @reconnect = original
311
333
  end
312
334
 
313
335
  def without_reconnect(&blk)
314
336
  with_reconnect(false, &blk)
315
337
  end
316
338
 
317
- protected
339
+ protected
318
340
 
319
341
  def logging(commands)
320
- return yield unless @logger && @logger.debug?
342
+ return yield unless @logger&.debug?
321
343
 
322
344
  begin
323
345
  commands.each do |name, *args|
324
346
  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
347
+ if a.respond_to?(:inspect) then a.inspect
348
+ elsif a.respond_to?(:to_s) then a.to_s
328
349
  else
329
350
  # handle poorly-behaved descendants of BasicObject
330
351
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -358,9 +379,9 @@ class Redis
358
379
  Errno::ENETUNREACH,
359
380
  Errno::ENOENT,
360
381
  Errno::ETIMEDOUT,
361
- Errno::EINVAL
382
+ Errno::EINVAL => error
362
383
 
363
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
384
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
364
385
  end
365
386
 
366
387
  def ensure_connected
@@ -374,9 +395,9 @@ class Redis
374
395
  if connected?
375
396
  unless inherit_socket? || Process.pid == @pid
376
397
  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."
398
+ "Tried to use a connection from a child process without reconnecting. " \
399
+ "You need to reconnect to Redis after forking " \
400
+ "or set :inherit_socket to true."
380
401
  end
381
402
  else
382
403
  connect
@@ -387,7 +408,7 @@ class Redis
387
408
  disconnect
388
409
 
389
410
  if attempts <= @options[:reconnect_attempts] && @reconnect
390
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
411
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
391
412
  @options[:reconnect_delay_max]].min
392
413
 
393
414
  Kernel.sleep(sleep_t)
@@ -409,16 +430,14 @@ class Redis
409
430
 
410
431
  defaults.keys.each do |key|
411
432
  # Fill in defaults if needed
412
- if defaults[key].respond_to?(:call)
413
- defaults[key] = defaults[key].call
414
- end
433
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
415
434
 
416
435
  # Symbolize only keys that are needed
417
- options[key] = options[key.to_s] if options.has_key?(key.to_s)
436
+ options[key] = options[key.to_s] if options.key?(key.to_s)
418
437
  end
419
438
 
420
439
  url = options[:url]
421
- url = defaults[:url] if url == nil
440
+ url = defaults[:url] if url.nil?
422
441
 
423
442
  # Override defaults from URL if given
424
443
  if url
@@ -427,12 +446,13 @@ class Redis
427
446
  uri = URI(url)
428
447
 
429
448
  if uri.scheme == "unix"
430
- defaults[:path] = uri.path
449
+ defaults[:path] = uri.path
431
450
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
432
451
  defaults[:scheme] = uri.scheme
433
452
  defaults[:host] = uri.host if uri.host
434
453
  defaults[:port] = uri.port if uri.port
435
- defaults[:password] = CGI.unescape(uri.password) if uri.password
454
+ defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
455
+ defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
436
456
  defaults[:db] = uri.path[1..-1].to_i if uri.path
437
457
  defaults[:role] = :master
438
458
  else
@@ -458,7 +478,7 @@ class Redis
458
478
  options[:port] = options[:port].to_i
459
479
  end
460
480
 
461
- if options.has_key?(:timeout)
481
+ if options.key?(:timeout)
462
482
  options[:connect_timeout] ||= options[:timeout]
463
483
  options[:read_timeout] ||= options[:timeout]
464
484
  options[:write_timeout] ||= options[:timeout]
@@ -477,7 +497,7 @@ class Redis
477
497
 
478
498
  case options[:tcp_keepalive]
479
499
  when Hash
480
- [:time, :intvl, :probes].each do |key|
500
+ %i[time intvl probes].each do |key|
481
501
  unless options[:tcp_keepalive][key].is_a?(Integer)
482
502
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
483
503
  end
@@ -485,13 +505,13 @@ class Redis
485
505
 
486
506
  when Integer
487
507
  if options[:tcp_keepalive] >= 60
488
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
508
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
489
509
 
490
510
  elsif options[:tcp_keepalive] >= 30
491
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
511
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
492
512
 
493
513
  elsif options[:tcp_keepalive] >= 5
494
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
514
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
495
515
  end
496
516
  end
497
517
 
@@ -503,14 +523,14 @@ class Redis
503
523
  def _parse_driver(driver)
504
524
  driver = driver.to_s if driver.is_a?(Symbol)
505
525
 
506
- if driver.kind_of?(String)
526
+ if driver.is_a?(String)
507
527
  begin
508
528
  require_relative "connection/#{driver}"
509
- rescue LoadError, NameError => e
529
+ rescue LoadError, NameError
510
530
  begin
511
- require "connection/#{driver}"
512
- rescue LoadError, NameError => e
513
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
531
+ require "redis/connection/#{driver}"
532
+ rescue LoadError, NameError => error
533
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
514
534
  end
515
535
  end
516
536
 
@@ -529,8 +549,7 @@ class Redis
529
549
  @options
530
550
  end
531
551
 
532
- def check(client)
533
- end
552
+ def check(client); end
534
553
 
535
554
  class Sentinel < Connector
536
555
  def initialize(options)
@@ -539,7 +558,7 @@ class Redis
539
558
  @options[:db] = DEFAULTS.fetch(:db)
540
559
 
541
560
  @sentinels = @options.delete(:sentinels).dup
542
- @role = @options.fetch(:role, "master").to_s
561
+ @role = (@options[:role] || "master").to_s
543
562
  @master = @options[:host]
544
563
  end
545
564
 
@@ -562,13 +581,13 @@ class Redis
562
581
 
563
582
  def resolve
564
583
  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
584
+ when "master"
585
+ resolve_master
586
+ when "slave"
587
+ resolve_slave
588
+ else
589
+ raise ArgumentError, "Unknown instance role #{@role}"
590
+ end
572
591
 
573
592
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
574
593
  end
@@ -576,11 +595,12 @@ class Redis
576
595
  def sentinel_detect
577
596
  @sentinels.each do |sentinel|
578
597
  client = Client.new(@options.merge({
579
- :host => sentinel[:host],
580
- :port => sentinel[:port],
581
- password: sentinel[:password],
582
- :reconnect_attempts => 0,
583
- }))
598
+ host: sentinel[:host] || sentinel["host"],
599
+ port: sentinel[:port] || sentinel["port"],
600
+ username: sentinel[:username] || sentinel["username"],
601
+ password: sentinel[:password] || sentinel["password"],
602
+ reconnect_attempts: 0
603
+ }))
584
604
 
585
605
  begin
586
606
  if result = yield(client)
@@ -602,7 +622,7 @@ class Redis
602
622
  def resolve_master
603
623
  sentinel_detect do |client|
604
624
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
605
- {:host => reply[0], :port => reply[1]}
625
+ { host: reply[0], port: reply[1] }
606
626
  end
607
627
  end
608
628
  end
@@ -620,7 +640,7 @@ class Redis
620
640
  slave = slaves.sample
621
641
  {
622
642
  host: slave.fetch('ip'),
623
- port: slave.fetch('port'),
643
+ port: slave.fetch('port')
624
644
  }
625
645
  end
626
646
  end
data/lib/redis/cluster.rb CHANGED
@@ -78,10 +78,13 @@ class Redis
78
78
  end
79
79
 
80
80
  def call_pipeline(pipeline)
81
- node_keys, command_keys = extract_keys_in_pipeline(pipeline)
82
- raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
83
- node = find_node(node_keys.first)
84
- try_send(node, :call_pipeline, pipeline)
81
+ node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
82
+ if node_keys.size > 1
83
+ raise(CrossSlotPipeliningError,
84
+ pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
85
+ end
86
+
87
+ try_send(find_node(node_keys.first), :call_pipeline, pipeline)
85
88
  end
86
89
 
87
90
  def call_with_timeout(command, timeout, &block)
@@ -127,7 +130,7 @@ class Redis
127
130
  def send_command(command, &block)
128
131
  cmd = command.first.to_s.downcase
129
132
  case cmd
130
- when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
133
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
131
134
  @node.call_all(command, &block).first
132
135
  when 'flushall', 'flushdb'
133
136
  @node.call_master(command, &block).first
@@ -216,11 +219,13 @@ class Redis
216
219
  rescue CommandError => err
217
220
  if err.message.start_with?('MOVED')
218
221
  raise if retry_count <= 0
222
+
219
223
  node = assign_redirection_node(err.message)
220
224
  retry_count -= 1
221
225
  retry
222
226
  elsif err.message.start_with?('ASK')
223
227
  raise if retry_count <= 0
228
+
224
229
  node = assign_asking_node(err.message)
225
230
  node.call(%i[asking])
226
231
  retry_count -= 1
@@ -250,14 +255,14 @@ class Redis
250
255
  find_node(node_key)
251
256
  end
252
257
 
253
- def find_node_key(command)
258
+ def find_node_key(command, primary_only: false)
254
259
  key = @command.extract_first_key(command)
255
260
  return if key.empty?
256
261
 
257
262
  slot = KeySlotConverter.convert(key)
258
263
  return unless @slot.exists?(slot)
259
264
 
260
- if @command.should_send_to_master?(command)
265
+ if @command.should_send_to_master?(command) || primary_only
261
266
  @slot.find_node_key_of_master(slot)
262
267
  else
263
268
  @slot.find_node_key_of_slave(slot)
@@ -266,6 +271,7 @@ class Redis
266
271
 
267
272
  def find_node(node_key)
268
273
  return @node.sample if node_key.nil?
274
+
269
275
  @node.find_by(node_key)
270
276
  rescue Node::ReloadNeeded
271
277
  update_cluster_info!(node_key)
@@ -281,11 +287,5 @@ class Redis
281
287
  @node.map(&:disconnect)
282
288
  @node, @slot = fetch_cluster_info!(@option)
283
289
  end
284
-
285
- def extract_keys_in_pipeline(pipeline)
286
- node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
287
- command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
288
- [node_keys, command_keys]
289
- end
290
290
  end
291
291
  end
@@ -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
@@ -74,8 +76,9 @@ class Redis
74
76
  clients = options.map do |node_key, option|
75
77
  next if replica_disabled? && slave?(node_key)
76
78
 
79
+ option = option.merge(readonly: true) if slave?(node_key)
80
+
77
81
  client = Client.new(option)
78
- client.call(%i[readonly]) if slave?(node_key)
79
82
  [node_key, client]
80
83
  end
81
84
 
@@ -97,6 +100,7 @@ class Redis
97
100
  end
98
101
 
99
102
  return results if errors.empty?
103
+
100
104
  raise CommandErrorCollection, errors
101
105
  end
102
106
  end