redis 4.1.2 → 4.2.2

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,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
@@ -226,6 +231,9 @@ class Redis
226
231
  else
227
232
  raise
228
233
  end
234
+ rescue CannotConnectError
235
+ update_cluster_info!
236
+ raise
229
237
  end
230
238
 
231
239
  def assign_redirection_node(err_msg)
@@ -261,6 +269,7 @@ class Redis
261
269
 
262
270
  def find_node(node_key)
263
271
  return @node.sample if node_key.nil?
272
+
264
273
  @node.find_by(node_key)
265
274
  rescue Node::ReloadNeeded
266
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