redis 4.1.3 → 4.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,12 +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
255
+ alias close disconnect
253
256
 
254
257
  def reconnect
255
258
  disconnect
@@ -300,30 +303,27 @@ class Redis
300
303
  with_socket_timeout(0, &blk)
301
304
  end
302
305
 
303
- def with_reconnect(val=true)
304
- begin
305
- original, @reconnect = @reconnect, val
306
- yield
307
- ensure
308
- @reconnect = original
309
- end
306
+ def with_reconnect(val = true)
307
+ original, @reconnect = @reconnect, val
308
+ yield
309
+ ensure
310
+ @reconnect = original
310
311
  end
311
312
 
312
313
  def without_reconnect(&blk)
313
314
  with_reconnect(false, &blk)
314
315
  end
315
316
 
316
- protected
317
+ protected
317
318
 
318
319
  def logging(commands)
319
- return yield unless @logger && @logger.debug?
320
+ return yield unless @logger&.debug?
320
321
 
321
322
  begin
322
323
  commands.each do |name, *args|
323
324
  logged_args = args.map do |a|
324
- case
325
- when a.respond_to?(:inspect) then a.inspect
326
- 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
327
327
  else
328
328
  # handle poorly-behaved descendants of BasicObject
329
329
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -357,9 +357,9 @@ class Redis
357
357
  Errno::ENETUNREACH,
358
358
  Errno::ENOENT,
359
359
  Errno::ETIMEDOUT,
360
- Errno::EINVAL
360
+ Errno::EINVAL => error
361
361
 
362
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
362
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
363
363
  end
364
364
 
365
365
  def ensure_connected
@@ -373,9 +373,9 @@ class Redis
373
373
  if connected?
374
374
  unless inherit_socket? || Process.pid == @pid
375
375
  raise InheritedError,
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."
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."
379
379
  end
380
380
  else
381
381
  connect
@@ -386,7 +386,7 @@ class Redis
386
386
  disconnect
387
387
 
388
388
  if attempts <= @options[:reconnect_attempts] && @reconnect
389
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
389
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
390
390
  @options[:reconnect_delay_max]].min
391
391
 
392
392
  Kernel.sleep(sleep_t)
@@ -408,16 +408,14 @@ class Redis
408
408
 
409
409
  defaults.keys.each do |key|
410
410
  # Fill in defaults if needed
411
- if defaults[key].respond_to?(:call)
412
- defaults[key] = defaults[key].call
413
- end
411
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
414
412
 
415
413
  # Symbolize only keys that are needed
416
- 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)
417
415
  end
418
416
 
419
417
  url = options[:url]
420
- url = defaults[:url] if url == nil
418
+ url = defaults[:url] if url.nil?
421
419
 
422
420
  # Override defaults from URL if given
423
421
  if url
@@ -426,7 +424,7 @@ class Redis
426
424
  uri = URI(url)
427
425
 
428
426
  if uri.scheme == "unix"
429
- defaults[:path] = uri.path
427
+ defaults[:path] = uri.path
430
428
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
431
429
  defaults[:scheme] = uri.scheme
432
430
  defaults[:host] = uri.host if uri.host
@@ -457,7 +455,7 @@ class Redis
457
455
  options[:port] = options[:port].to_i
458
456
  end
459
457
 
460
- if options.has_key?(:timeout)
458
+ if options.key?(:timeout)
461
459
  options[:connect_timeout] ||= options[:timeout]
462
460
  options[:read_timeout] ||= options[:timeout]
463
461
  options[:write_timeout] ||= options[:timeout]
@@ -476,7 +474,7 @@ class Redis
476
474
 
477
475
  case options[:tcp_keepalive]
478
476
  when Hash
479
- [:time, :intvl, :probes].each do |key|
477
+ %i[time intvl probes].each do |key|
480
478
  unless options[:tcp_keepalive][key].is_a?(Integer)
481
479
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
482
480
  end
@@ -484,13 +482,13 @@ class Redis
484
482
 
485
483
  when Integer
486
484
  if options[:tcp_keepalive] >= 60
487
- 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 }
488
486
 
489
487
  elsif options[:tcp_keepalive] >= 30
490
- 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 }
491
489
 
492
490
  elsif options[:tcp_keepalive] >= 5
493
- 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 }
494
492
  end
495
493
  end
496
494
 
@@ -502,14 +500,14 @@ class Redis
502
500
  def _parse_driver(driver)
503
501
  driver = driver.to_s if driver.is_a?(Symbol)
504
502
 
505
- if driver.kind_of?(String)
503
+ if driver.is_a?(String)
506
504
  begin
507
505
  require_relative "connection/#{driver}"
508
- rescue LoadError, NameError => e
506
+ rescue LoadError, NameError
509
507
  begin
510
508
  require "connection/#{driver}"
511
- rescue LoadError, NameError => e
512
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
509
+ rescue LoadError, NameError => error
510
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
513
511
  end
514
512
  end
515
513
 
@@ -528,8 +526,7 @@ class Redis
528
526
  @options
529
527
  end
530
528
 
531
- def check(client)
532
- end
529
+ def check(client); end
533
530
 
534
531
  class Sentinel < Connector
535
532
  def initialize(options)
@@ -538,7 +535,7 @@ class Redis
538
535
  @options[:db] = DEFAULTS.fetch(:db)
539
536
 
540
537
  @sentinels = @options.delete(:sentinels).dup
541
- @role = @options.fetch(:role, "master").to_s
538
+ @role = (@options[:role] || "master").to_s
542
539
  @master = @options[:host]
543
540
  end
544
541
 
@@ -561,13 +558,13 @@ class Redis
561
558
 
562
559
  def resolve
563
560
  result = case @role
564
- when "master"
565
- resolve_master
566
- when "slave"
567
- resolve_slave
568
- else
569
- raise ArgumentError, "Unknown instance role #{@role}"
570
- end
561
+ when "master"
562
+ resolve_master
563
+ when "slave"
564
+ resolve_slave
565
+ else
566
+ raise ArgumentError, "Unknown instance role #{@role}"
567
+ end
571
568
 
572
569
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
573
570
  end
@@ -575,11 +572,11 @@ class Redis
575
572
  def sentinel_detect
576
573
  @sentinels.each do |sentinel|
577
574
  client = Client.new(@options.merge({
578
- :host => sentinel[:host],
579
- :port => sentinel[:port],
580
- password: sentinel[:password],
581
- :reconnect_attempts => 0,
582
- }))
575
+ host: sentinel[:host] || sentinel["host"],
576
+ port: sentinel[:port] || sentinel["port"],
577
+ password: sentinel[:password] || sentinel["password"],
578
+ reconnect_attempts: 0
579
+ }))
583
580
 
584
581
  begin
585
582
  if result = yield(client)
@@ -601,7 +598,7 @@ class Redis
601
598
  def resolve_master
602
599
  sentinel_detect do |client|
603
600
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
604
- {:host => reply[0], :port => reply[1]}
601
+ { host: reply[0], port: reply[1] }
605
602
  end
606
603
  end
607
604
  end
@@ -619,7 +616,7 @@ class Redis
619
616
  slave = slaves.sample
620
617
  {
621
618
  host: slave.fetch('ip'),
622
- port: slave.fetch('port'),
619
+ port: slave.fetch('port')
623
620
  }
624
621
  end
625
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
@@ -112,12 +113,11 @@ class Redis
112
113
  node = Node.new(option.per_node_key)
113
114
  available_slots = SlotLoader.load(node)
114
115
  node_flags = NodeLoader.load_flags(node)
115
- available_node_urls = NodeKey.to_node_urls(available_slots.keys, secure: option.secure?)
116
- option.update_node(available_node_urls)
116
+ option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
117
117
  [Node.new(option.per_node_key, node_flags, option.use_replica?),
118
118
  Slot.new(available_slots, node_flags, option.use_replica?)]
119
119
  ensure
120
- node.map(&:disconnect)
120
+ node&.each(&:disconnect)
121
121
  end
122
122
 
123
123
  def fetch_command_details(nodes)
@@ -216,9 +216,14 @@ class Redis
216
216
  node.public_send(method_name, *args, &block)
217
217
  rescue CommandError => err
218
218
  if err.message.start_with?('MOVED')
219
- assign_redirection_node(err.message).public_send(method_name, *args, &block)
219
+ raise if retry_count <= 0
220
+
221
+ node = assign_redirection_node(err.message)
222
+ retry_count -= 1
223
+ retry
220
224
  elsif err.message.start_with?('ASK')
221
225
  raise if retry_count <= 0
226
+
222
227
  node = assign_asking_node(err.message)
223
228
  node.call(%i[asking])
224
229
  retry_count -= 1
@@ -264,6 +269,7 @@ class Redis
264
269
 
265
270
  def find_node(node_key)
266
271
  return @node.sample if node_key.nil?
272
+
267
273
  @node.find_by(node_key)
268
274
  rescue Node::ReloadNeeded
269
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
@@ -6,17 +6,13 @@ class Redis
6
6
  # It is different from node id.
7
7
  # Node id is internal identifying code in Redis Cluster.
8
8
  module NodeKey
9
- DEFAULT_SCHEME = 'redis'
10
- SECURE_SCHEME = 'rediss'
11
9
  DELIMITER = ':'
12
10
 
13
11
  module_function
14
12
 
15
- def to_node_urls(node_keys, secure:)
16
- scheme = secure ? SECURE_SCHEME : DEFAULT_SCHEME
17
- node_keys
18
- .map { |k| k.split(DELIMITER) }
19
- .map { |k| URI::Generic.build(scheme: scheme, host: k[0], port: k[1].to_i).to_s }
13
+ def optionize(node_key)
14
+ host, port = split(node_key)
15
+ { host: host, port: port }
20
16
  end
21
17
 
22
18
  def split(node_key)
@@ -15,36 +15,35 @@ class Redis
15
15
  def initialize(options)
16
16
  options = options.dup
17
17
  node_addrs = options.delete(:cluster)
18
- @node_uris = build_node_uris(node_addrs)
18
+ @node_opts = build_node_options(node_addrs)
19
19
  @replica = options.delete(:replica) == true
20
+ add_common_node_option_if_needed(options, @node_opts, :scheme)
21
+ add_common_node_option_if_needed(options, @node_opts, :password)
20
22
  @options = options
21
23
  end
22
24
 
23
25
  def per_node_key
24
- @node_uris.map { |uri| [NodeKey.build_from_uri(uri), @options.merge(url: uri.to_s)] }
26
+ @node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
25
27
  .to_h
26
28
  end
27
29
 
28
- def secure?
29
- @node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false
30
- end
31
-
32
30
  def use_replica?
33
31
  @replica
34
32
  end
35
33
 
36
34
  def update_node(addrs)
37
- @node_uris = build_node_uris(addrs)
35
+ @node_opts = build_node_options(addrs)
38
36
  end
39
37
 
40
38
  def add_node(host, port)
41
- @node_uris << parse_node_hash(host: host, port: port)
39
+ @node_opts << { host: host, port: port }
42
40
  end
43
41
 
44
42
  private
45
43
 
46
- def build_node_uris(addrs)
44
+ def build_node_options(addrs)
47
45
  raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
46
+
48
47
  addrs.map { |addr| parse_node_addr(addr) }
49
48
  end
50
49
 
@@ -53,7 +52,7 @@ class Redis
53
52
  when String
54
53
  parse_node_url(addr)
55
54
  when Hash
56
- parse_node_hash(addr)
55
+ parse_node_option(addr)
57
56
  else
58
57
  raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
59
58
  end
@@ -62,15 +61,29 @@ class Redis
62
61
  def parse_node_url(addr)
63
62
  uri = URI(addr)
64
63
  raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
65
- uri
64
+
65
+ db = uri.path.split('/')[1]&.to_i
66
+ { scheme: uri.scheme, password: uri.password, host: uri.host, port: uri.port, db: db }.reject { |_, v| v.nil? }
66
67
  rescue URI::InvalidURIError => err
67
68
  raise InvalidClientOptionError, err.message
68
69
  end
69
70
 
70
- def parse_node_hash(addr)
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
- URI::Generic.build(scheme: DEFAULT_SCHEME, host: addr[:host], port: addr[:port].to_i)
73
+ if addr.values_at(:host, :port).any?(&:nil?)
74
+ raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
75
+ end
76
+
77
+ addr
78
+ end
79
+
80
+ # Redis cluster node returns only host and port information.
81
+ # So we should complement additional information such as:
82
+ # scheme, password and so on.
83
+ def add_common_node_option_if_needed(options, node_opts, key)
84
+ return options if options[key].nil? && node_opts.first[key].nil?
85
+
86
+ options[key] ||= node_opts.first[key]
74
87
  end
75
88
  end
76
89
  end