redis 4.1.4 → 4.2.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.
@@ -6,24 +6,25 @@ require "cgi"
6
6
 
7
7
  class Redis
8
8
  class Client
9
-
10
9
  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
- }
10
+ url: -> { ENV["REDIS_URL"] },
11
+ scheme: "redis",
12
+ host: "127.0.0.1",
13
+ port: 6379,
14
+ path: nil,
15
+ timeout: 5.0,
16
+ password: nil,
17
+ db: 0,
18
+ driver: nil,
19
+ id: nil,
20
+ tcp_keepalive: 0,
21
+ reconnect_attempts: 1,
22
+ reconnect_delay: 0,
23
+ reconnect_delay_max: 0.5,
24
+ inherit_socket: false,
25
+ sentinels: nil,
26
+ role: nil
27
+ }.freeze
27
28
 
28
29
  attr_reader :options
29
30
 
@@ -89,7 +90,7 @@ class Redis
89
90
  @pending_reads = 0
90
91
 
91
92
  @connector =
92
- if options.include?(:sentinels)
93
+ if !@options[:sentinels].nil?
93
94
  Connector::Sentinel.new(@options)
94
95
  elsif options.include?(:connector) && options[:connector].respond_to?(:new)
95
96
  options.delete(:connector).new(@options)
@@ -166,6 +167,7 @@ class Redis
166
167
  end
167
168
  rescue ConnectionError => e
168
169
  return nil if pipeline.shutdown?
170
+
169
171
  # Assume the pipeline was sent in one piece, but execution of
170
172
  # SHUTDOWN caused none of the replies for commands that were executed
171
173
  # prior to it from coming back around.
@@ -244,13 +246,13 @@ class Redis
244
246
  end
245
247
 
246
248
  def connected?
247
- !! (connection && connection.connected?)
249
+ !!(connection && connection.connected?)
248
250
  end
249
251
 
250
252
  def disconnect
251
253
  connection.disconnect if connected?
252
254
  end
253
- alias_method :close, :disconnect
255
+ alias close disconnect
254
256
 
255
257
  def reconnect
256
258
  disconnect
@@ -301,30 +303,27 @@ class Redis
301
303
  with_socket_timeout(0, &blk)
302
304
  end
303
305
 
304
- def with_reconnect(val=true)
305
- begin
306
- original, @reconnect = @reconnect, val
307
- yield
308
- ensure
309
- @reconnect = original
310
- end
306
+ def with_reconnect(val = true)
307
+ original, @reconnect = @reconnect, val
308
+ yield
309
+ ensure
310
+ @reconnect = original
311
311
  end
312
312
 
313
313
  def without_reconnect(&blk)
314
314
  with_reconnect(false, &blk)
315
315
  end
316
316
 
317
- protected
317
+ protected
318
318
 
319
319
  def logging(commands)
320
- return yield unless @logger && @logger.debug?
320
+ return yield unless @logger&.debug?
321
321
 
322
322
  begin
323
323
  commands.each do |name, *args|
324
324
  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
325
+ if a.respond_to?(:inspect) then a.inspect
326
+ elsif a.respond_to?(:to_s) then a.to_s
328
327
  else
329
328
  # handle poorly-behaved descendants of BasicObject
330
329
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -358,9 +357,9 @@ class Redis
358
357
  Errno::ENETUNREACH,
359
358
  Errno::ENOENT,
360
359
  Errno::ETIMEDOUT,
361
- Errno::EINVAL
360
+ Errno::EINVAL => error
362
361
 
363
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
362
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
364
363
  end
365
364
 
366
365
  def ensure_connected
@@ -374,9 +373,9 @@ class Redis
374
373
  if connected?
375
374
  unless inherit_socket? || Process.pid == @pid
376
375
  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."
376
+ "Tried to use a connection from a child process without reconnecting. " \
377
+ "You need to reconnect to Redis after forking " \
378
+ "or set :inherit_socket to true."
380
379
  end
381
380
  else
382
381
  connect
@@ -387,7 +386,7 @@ class Redis
387
386
  disconnect
388
387
 
389
388
  if attempts <= @options[:reconnect_attempts] && @reconnect
390
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
389
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
391
390
  @options[:reconnect_delay_max]].min
392
391
 
393
392
  Kernel.sleep(sleep_t)
@@ -409,16 +408,14 @@ class Redis
409
408
 
410
409
  defaults.keys.each do |key|
411
410
  # Fill in defaults if needed
412
- if defaults[key].respond_to?(:call)
413
- defaults[key] = defaults[key].call
414
- end
411
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
415
412
 
416
413
  # Symbolize only keys that are needed
417
- options[key] = options[key.to_s] if options.has_key?(key.to_s)
414
+ options[key] = options[key.to_s] if options.key?(key.to_s)
418
415
  end
419
416
 
420
417
  url = options[:url]
421
- url = defaults[:url] if url == nil
418
+ url = defaults[:url] if url.nil?
422
419
 
423
420
  # Override defaults from URL if given
424
421
  if url
@@ -427,7 +424,7 @@ class Redis
427
424
  uri = URI(url)
428
425
 
429
426
  if uri.scheme == "unix"
430
- defaults[:path] = uri.path
427
+ defaults[:path] = uri.path
431
428
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
432
429
  defaults[:scheme] = uri.scheme
433
430
  defaults[:host] = uri.host if uri.host
@@ -458,7 +455,7 @@ class Redis
458
455
  options[:port] = options[:port].to_i
459
456
  end
460
457
 
461
- if options.has_key?(:timeout)
458
+ if options.key?(:timeout)
462
459
  options[:connect_timeout] ||= options[:timeout]
463
460
  options[:read_timeout] ||= options[:timeout]
464
461
  options[:write_timeout] ||= options[:timeout]
@@ -477,7 +474,7 @@ class Redis
477
474
 
478
475
  case options[:tcp_keepalive]
479
476
  when Hash
480
- [:time, :intvl, :probes].each do |key|
477
+ %i[time intvl probes].each do |key|
481
478
  unless options[:tcp_keepalive][key].is_a?(Integer)
482
479
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
483
480
  end
@@ -485,13 +482,13 @@ class Redis
485
482
 
486
483
  when Integer
487
484
  if options[:tcp_keepalive] >= 60
488
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
485
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
489
486
 
490
487
  elsif options[:tcp_keepalive] >= 30
491
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
488
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
492
489
 
493
490
  elsif options[:tcp_keepalive] >= 5
494
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
491
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
495
492
  end
496
493
  end
497
494
 
@@ -503,14 +500,14 @@ class Redis
503
500
  def _parse_driver(driver)
504
501
  driver = driver.to_s if driver.is_a?(Symbol)
505
502
 
506
- if driver.kind_of?(String)
503
+ if driver.is_a?(String)
507
504
  begin
508
505
  require_relative "connection/#{driver}"
509
- rescue LoadError, NameError => e
506
+ rescue LoadError, NameError
510
507
  begin
511
508
  require "connection/#{driver}"
512
- rescue LoadError, NameError => e
513
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
509
+ rescue LoadError, NameError => error
510
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
514
511
  end
515
512
  end
516
513
 
@@ -529,8 +526,7 @@ class Redis
529
526
  @options
530
527
  end
531
528
 
532
- def check(client)
533
- end
529
+ def check(client); end
534
530
 
535
531
  class Sentinel < Connector
536
532
  def initialize(options)
@@ -539,7 +535,7 @@ class Redis
539
535
  @options[:db] = DEFAULTS.fetch(:db)
540
536
 
541
537
  @sentinels = @options.delete(:sentinels).dup
542
- @role = @options.fetch(:role, "master").to_s
538
+ @role = (@options[:role] || "master").to_s
543
539
  @master = @options[:host]
544
540
  end
545
541
 
@@ -562,13 +558,13 @@ class Redis
562
558
 
563
559
  def resolve
564
560
  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
561
+ when "master"
562
+ resolve_master
563
+ when "slave"
564
+ resolve_slave
565
+ else
566
+ raise ArgumentError, "Unknown instance role #{@role}"
567
+ end
572
568
 
573
569
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
574
570
  end
@@ -576,11 +572,11 @@ class Redis
576
572
  def sentinel_detect
577
573
  @sentinels.each do |sentinel|
578
574
  client = Client.new(@options.merge({
579
- :host => sentinel[:host],
580
- :port => sentinel[:port],
581
- password: sentinel[:password],
582
- :reconnect_attempts => 0,
583
- }))
575
+ host: sentinel[:host] || sentinel["host"],
576
+ port: sentinel[:port] || sentinel["port"],
577
+ password: sentinel[:password] || sentinel["password"],
578
+ reconnect_attempts: 0
579
+ }))
584
580
 
585
581
  begin
586
582
  if result = yield(client)
@@ -602,7 +598,7 @@ class Redis
602
598
  def resolve_master
603
599
  sentinel_detect do |client|
604
600
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
605
- {:host => reply[0], :port => reply[1]}
601
+ { host: reply[0], port: reply[1] }
606
602
  end
607
603
  end
608
604
  end
@@ -620,7 +616,7 @@ class Redis
620
616
  slave = slaves.sample
621
617
  {
622
618
  host: slave.fetch('ip'),
623
- port: slave.fetch('port'),
619
+ port: slave.fetch('port')
624
620
  }
625
621
  end
626
622
  end
@@ -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
@@ -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
@@ -43,6 +43,7 @@ class Redis
43
43
 
44
44
  def build_node_options(addrs)
45
45
  raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
46
+
46
47
  addrs.map { |addr| parse_node_addr(addr) }
47
48
  end
48
49
 
@@ -69,7 +70,9 @@ class Redis
69
70
 
70
71
  def parse_node_option(addr)
71
72
  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?)
73
+ if addr.values_at(:host, :port).any?(&:nil?)
74
+ raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
75
+ end
73
76
 
74
77
  addr
75
78
  end
@@ -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