redis 4.1.4 → 4.2.4

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,30 @@ 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
+ password: nil,
21
+ db: 0,
22
+ driver: nil,
23
+ id: nil,
24
+ tcp_keepalive: 0,
25
+ reconnect_attempts: 1,
26
+ reconnect_delay: 0,
27
+ reconnect_delay_max: 0.5,
28
+ inherit_socket: false,
29
+ logger: nil,
30
+ sentinels: nil,
31
+ role: nil
32
+ }.freeze
27
33
 
28
34
  attr_reader :options
29
35
 
@@ -89,7 +95,7 @@ class Redis
89
95
  @pending_reads = 0
90
96
 
91
97
  @connector =
92
- if options.include?(:sentinels)
98
+ if !@options[:sentinels].nil?
93
99
  Connector::Sentinel.new(@options)
94
100
  elsif options.include?(:connector) && options[:connector].respond_to?(:new)
95
101
  options.delete(:connector).new(@options)
@@ -166,6 +172,7 @@ class Redis
166
172
  end
167
173
  rescue ConnectionError => e
168
174
  return nil if pipeline.shutdown?
175
+
169
176
  # Assume the pipeline was sent in one piece, but execution of
170
177
  # SHUTDOWN caused none of the replies for commands that were executed
171
178
  # prior to it from coming back around.
@@ -244,13 +251,13 @@ class Redis
244
251
  end
245
252
 
246
253
  def connected?
247
- !! (connection && connection.connected?)
254
+ !!(connection && connection.connected?)
248
255
  end
249
256
 
250
257
  def disconnect
251
258
  connection.disconnect if connected?
252
259
  end
253
- alias_method :close, :disconnect
260
+ alias close disconnect
254
261
 
255
262
  def reconnect
256
263
  disconnect
@@ -301,30 +308,27 @@ class Redis
301
308
  with_socket_timeout(0, &blk)
302
309
  end
303
310
 
304
- def with_reconnect(val=true)
305
- begin
306
- original, @reconnect = @reconnect, val
307
- yield
308
- ensure
309
- @reconnect = original
310
- end
311
+ def with_reconnect(val = true)
312
+ original, @reconnect = @reconnect, val
313
+ yield
314
+ ensure
315
+ @reconnect = original
311
316
  end
312
317
 
313
318
  def without_reconnect(&blk)
314
319
  with_reconnect(false, &blk)
315
320
  end
316
321
 
317
- protected
322
+ protected
318
323
 
319
324
  def logging(commands)
320
- return yield unless @logger && @logger.debug?
325
+ return yield unless @logger&.debug?
321
326
 
322
327
  begin
323
328
  commands.each do |name, *args|
324
329
  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
330
+ if a.respond_to?(:inspect) then a.inspect
331
+ elsif a.respond_to?(:to_s) then a.to_s
328
332
  else
329
333
  # handle poorly-behaved descendants of BasicObject
330
334
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -358,9 +362,9 @@ class Redis
358
362
  Errno::ENETUNREACH,
359
363
  Errno::ENOENT,
360
364
  Errno::ETIMEDOUT,
361
- Errno::EINVAL
365
+ Errno::EINVAL => error
362
366
 
363
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
367
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
364
368
  end
365
369
 
366
370
  def ensure_connected
@@ -374,9 +378,9 @@ class Redis
374
378
  if connected?
375
379
  unless inherit_socket? || Process.pid == @pid
376
380
  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."
381
+ "Tried to use a connection from a child process without reconnecting. " \
382
+ "You need to reconnect to Redis after forking " \
383
+ "or set :inherit_socket to true."
380
384
  end
381
385
  else
382
386
  connect
@@ -387,7 +391,7 @@ class Redis
387
391
  disconnect
388
392
 
389
393
  if attempts <= @options[:reconnect_attempts] && @reconnect
390
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
394
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
391
395
  @options[:reconnect_delay_max]].min
392
396
 
393
397
  Kernel.sleep(sleep_t)
@@ -409,16 +413,14 @@ class Redis
409
413
 
410
414
  defaults.keys.each do |key|
411
415
  # Fill in defaults if needed
412
- if defaults[key].respond_to?(:call)
413
- defaults[key] = defaults[key].call
414
- end
416
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
415
417
 
416
418
  # Symbolize only keys that are needed
417
- options[key] = options[key.to_s] if options.has_key?(key.to_s)
419
+ options[key] = options[key.to_s] if options.key?(key.to_s)
418
420
  end
419
421
 
420
422
  url = options[:url]
421
- url = defaults[:url] if url == nil
423
+ url = defaults[:url] if url.nil?
422
424
 
423
425
  # Override defaults from URL if given
424
426
  if url
@@ -427,7 +429,7 @@ class Redis
427
429
  uri = URI(url)
428
430
 
429
431
  if uri.scheme == "unix"
430
- defaults[:path] = uri.path
432
+ defaults[:path] = uri.path
431
433
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
432
434
  defaults[:scheme] = uri.scheme
433
435
  defaults[:host] = uri.host if uri.host
@@ -458,7 +460,7 @@ class Redis
458
460
  options[:port] = options[:port].to_i
459
461
  end
460
462
 
461
- if options.has_key?(:timeout)
463
+ if options.key?(:timeout)
462
464
  options[:connect_timeout] ||= options[:timeout]
463
465
  options[:read_timeout] ||= options[:timeout]
464
466
  options[:write_timeout] ||= options[:timeout]
@@ -477,7 +479,7 @@ class Redis
477
479
 
478
480
  case options[:tcp_keepalive]
479
481
  when Hash
480
- [:time, :intvl, :probes].each do |key|
482
+ %i[time intvl probes].each do |key|
481
483
  unless options[:tcp_keepalive][key].is_a?(Integer)
482
484
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
483
485
  end
@@ -485,13 +487,13 @@ class Redis
485
487
 
486
488
  when Integer
487
489
  if options[:tcp_keepalive] >= 60
488
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
490
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
489
491
 
490
492
  elsif options[:tcp_keepalive] >= 30
491
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
493
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
492
494
 
493
495
  elsif options[:tcp_keepalive] >= 5
494
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
496
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
495
497
  end
496
498
  end
497
499
 
@@ -503,14 +505,14 @@ class Redis
503
505
  def _parse_driver(driver)
504
506
  driver = driver.to_s if driver.is_a?(Symbol)
505
507
 
506
- if driver.kind_of?(String)
508
+ if driver.is_a?(String)
507
509
  begin
508
510
  require_relative "connection/#{driver}"
509
- rescue LoadError, NameError => e
511
+ rescue LoadError, NameError
510
512
  begin
511
513
  require "connection/#{driver}"
512
- rescue LoadError, NameError => e
513
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
514
+ rescue LoadError, NameError => error
515
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
514
516
  end
515
517
  end
516
518
 
@@ -529,8 +531,7 @@ class Redis
529
531
  @options
530
532
  end
531
533
 
532
- def check(client)
533
- end
534
+ def check(client); end
534
535
 
535
536
  class Sentinel < Connector
536
537
  def initialize(options)
@@ -539,7 +540,7 @@ class Redis
539
540
  @options[:db] = DEFAULTS.fetch(:db)
540
541
 
541
542
  @sentinels = @options.delete(:sentinels).dup
542
- @role = @options.fetch(:role, "master").to_s
543
+ @role = (@options[:role] || "master").to_s
543
544
  @master = @options[:host]
544
545
  end
545
546
 
@@ -562,13 +563,13 @@ class Redis
562
563
 
563
564
  def resolve
564
565
  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
566
+ when "master"
567
+ resolve_master
568
+ when "slave"
569
+ resolve_slave
570
+ else
571
+ raise ArgumentError, "Unknown instance role #{@role}"
572
+ end
572
573
 
573
574
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
574
575
  end
@@ -576,11 +577,11 @@ class Redis
576
577
  def sentinel_detect
577
578
  @sentinels.each do |sentinel|
578
579
  client = Client.new(@options.merge({
579
- :host => sentinel[:host],
580
- :port => sentinel[:port],
581
- password: sentinel[:password],
582
- :reconnect_attempts => 0,
583
- }))
580
+ host: sentinel[:host] || sentinel["host"],
581
+ port: sentinel[:port] || sentinel["port"],
582
+ password: sentinel[:password] || sentinel["password"],
583
+ reconnect_attempts: 0
584
+ }))
584
585
 
585
586
  begin
586
587
  if result = yield(client)
@@ -602,7 +603,7 @@ class Redis
602
603
  def resolve_master
603
604
  sentinel_detect do |client|
604
605
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
605
- {:host => reply[0], :port => reply[1]}
606
+ { host: reply[0], port: reply[1] }
606
607
  end
607
608
  end
608
609
  end
@@ -620,7 +621,7 @@ class Redis
620
621
  slave = slaves.sample
621
622
  {
622
623
  host: slave.fetch('ip'),
623
- port: slave.fetch('port'),
624
+ port: slave.fetch('port')
624
625
  }
625
626
  end
626
627
  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