redis 4.1.3 → 4.2.5

Sign up to get free protection for your applications and to get access to all the features.
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