redis 4.1.3 → 4.2.5

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.
data/lib/redis/client.rb CHANGED
@@ -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,12 +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
260
+ alias close disconnect
253
261
 
254
262
  def reconnect
255
263
  disconnect
@@ -300,30 +308,27 @@ class Redis
300
308
  with_socket_timeout(0, &blk)
301
309
  end
302
310
 
303
- def with_reconnect(val=true)
304
- begin
305
- original, @reconnect = @reconnect, val
306
- yield
307
- ensure
308
- @reconnect = original
309
- end
311
+ def with_reconnect(val = true)
312
+ original, @reconnect = @reconnect, val
313
+ yield
314
+ ensure
315
+ @reconnect = original
310
316
  end
311
317
 
312
318
  def without_reconnect(&blk)
313
319
  with_reconnect(false, &blk)
314
320
  end
315
321
 
316
- protected
322
+ protected
317
323
 
318
324
  def logging(commands)
319
- return yield unless @logger && @logger.debug?
325
+ return yield unless @logger&.debug?
320
326
 
321
327
  begin
322
328
  commands.each do |name, *args|
323
329
  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
330
+ if a.respond_to?(:inspect) then a.inspect
331
+ elsif a.respond_to?(:to_s) then a.to_s
327
332
  else
328
333
  # handle poorly-behaved descendants of BasicObject
329
334
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -357,9 +362,9 @@ class Redis
357
362
  Errno::ENETUNREACH,
358
363
  Errno::ENOENT,
359
364
  Errno::ETIMEDOUT,
360
- Errno::EINVAL
365
+ Errno::EINVAL => error
361
366
 
362
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
367
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
363
368
  end
364
369
 
365
370
  def ensure_connected
@@ -373,9 +378,9 @@ class Redis
373
378
  if connected?
374
379
  unless inherit_socket? || Process.pid == @pid
375
380
  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."
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."
379
384
  end
380
385
  else
381
386
  connect
@@ -386,7 +391,7 @@ class Redis
386
391
  disconnect
387
392
 
388
393
  if attempts <= @options[:reconnect_attempts] && @reconnect
389
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
394
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
390
395
  @options[:reconnect_delay_max]].min
391
396
 
392
397
  Kernel.sleep(sleep_t)
@@ -408,16 +413,14 @@ class Redis
408
413
 
409
414
  defaults.keys.each do |key|
410
415
  # Fill in defaults if needed
411
- if defaults[key].respond_to?(:call)
412
- defaults[key] = defaults[key].call
413
- end
416
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
414
417
 
415
418
  # Symbolize only keys that are needed
416
- 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)
417
420
  end
418
421
 
419
422
  url = options[:url]
420
- url = defaults[:url] if url == nil
423
+ url = defaults[:url] if url.nil?
421
424
 
422
425
  # Override defaults from URL if given
423
426
  if url
@@ -426,7 +429,7 @@ class Redis
426
429
  uri = URI(url)
427
430
 
428
431
  if uri.scheme == "unix"
429
- defaults[:path] = uri.path
432
+ defaults[:path] = uri.path
430
433
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
431
434
  defaults[:scheme] = uri.scheme
432
435
  defaults[:host] = uri.host if uri.host
@@ -457,7 +460,7 @@ class Redis
457
460
  options[:port] = options[:port].to_i
458
461
  end
459
462
 
460
- if options.has_key?(:timeout)
463
+ if options.key?(:timeout)
461
464
  options[:connect_timeout] ||= options[:timeout]
462
465
  options[:read_timeout] ||= options[:timeout]
463
466
  options[:write_timeout] ||= options[:timeout]
@@ -476,7 +479,7 @@ class Redis
476
479
 
477
480
  case options[:tcp_keepalive]
478
481
  when Hash
479
- [:time, :intvl, :probes].each do |key|
482
+ %i[time intvl probes].each do |key|
480
483
  unless options[:tcp_keepalive][key].is_a?(Integer)
481
484
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
482
485
  end
@@ -484,13 +487,13 @@ class Redis
484
487
 
485
488
  when Integer
486
489
  if options[:tcp_keepalive] >= 60
487
- 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 }
488
491
 
489
492
  elsif options[:tcp_keepalive] >= 30
490
- 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 }
491
494
 
492
495
  elsif options[:tcp_keepalive] >= 5
493
- 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 }
494
497
  end
495
498
  end
496
499
 
@@ -502,14 +505,14 @@ class Redis
502
505
  def _parse_driver(driver)
503
506
  driver = driver.to_s if driver.is_a?(Symbol)
504
507
 
505
- if driver.kind_of?(String)
508
+ if driver.is_a?(String)
506
509
  begin
507
510
  require_relative "connection/#{driver}"
508
- rescue LoadError, NameError => e
511
+ rescue LoadError, NameError
509
512
  begin
510
513
  require "connection/#{driver}"
511
- rescue LoadError, NameError => e
512
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
514
+ rescue LoadError, NameError => error
515
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
513
516
  end
514
517
  end
515
518
 
@@ -528,8 +531,7 @@ class Redis
528
531
  @options
529
532
  end
530
533
 
531
- def check(client)
532
- end
534
+ def check(client); end
533
535
 
534
536
  class Sentinel < Connector
535
537
  def initialize(options)
@@ -538,7 +540,7 @@ class Redis
538
540
  @options[:db] = DEFAULTS.fetch(:db)
539
541
 
540
542
  @sentinels = @options.delete(:sentinels).dup
541
- @role = @options.fetch(:role, "master").to_s
543
+ @role = (@options[:role] || "master").to_s
542
544
  @master = @options[:host]
543
545
  end
544
546
 
@@ -561,13 +563,13 @@ class Redis
561
563
 
562
564
  def resolve
563
565
  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
566
+ when "master"
567
+ resolve_master
568
+ when "slave"
569
+ resolve_slave
570
+ else
571
+ raise ArgumentError, "Unknown instance role #{@role}"
572
+ end
571
573
 
572
574
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
573
575
  end
@@ -575,11 +577,11 @@ class Redis
575
577
  def sentinel_detect
576
578
  @sentinels.each do |sentinel|
577
579
  client = Client.new(@options.merge({
578
- :host => sentinel[:host],
579
- :port => sentinel[:port],
580
- password: sentinel[:password],
581
- :reconnect_attempts => 0,
582
- }))
580
+ host: sentinel[:host] || sentinel["host"],
581
+ port: sentinel[:port] || sentinel["port"],
582
+ password: sentinel[:password] || sentinel["password"],
583
+ reconnect_attempts: 0
584
+ }))
583
585
 
584
586
  begin
585
587
  if result = yield(client)
@@ -601,7 +603,7 @@ class Redis
601
603
  def resolve_master
602
604
  sentinel_detect do |client|
603
605
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
604
- {:host => reply[0], :port => reply[1]}
606
+ { host: reply[0], port: reply[1] }
605
607
  end
606
608
  end
607
609
  end
@@ -619,7 +621,7 @@ class Redis
619
621
  slave = slaves.sample
620
622
  {
621
623
  host: slave.fetch('ip'),
622
- port: slave.fetch('port'),
624
+ port: slave.fetch('port')
623
625
  }
624
626
  end
625
627
  end
data/lib/redis/cluster.rb CHANGED
@@ -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