redis 4.1.4 → 4.4.0

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,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