redis 3.3.5 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -2
  3. data/README.md +77 -76
  4. data/lib/redis.rb +779 -63
  5. data/lib/redis/client.rb +41 -20
  6. data/lib/redis/cluster.rb +286 -0
  7. data/lib/redis/cluster/command.rb +81 -0
  8. data/lib/redis/cluster/command_loader.rb +34 -0
  9. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  10. data/lib/redis/cluster/node.rb +104 -0
  11. data/lib/redis/cluster/node_key.rb +35 -0
  12. data/lib/redis/cluster/node_loader.rb +37 -0
  13. data/lib/redis/cluster/option.rb +77 -0
  14. data/lib/redis/cluster/slot.rb +69 -0
  15. data/lib/redis/cluster/slot_loader.rb +49 -0
  16. data/lib/redis/connection.rb +2 -2
  17. data/lib/redis/connection/command_helper.rb +2 -8
  18. data/lib/redis/connection/hiredis.rb +2 -2
  19. data/lib/redis/connection/ruby.rb +13 -30
  20. data/lib/redis/connection/synchrony.rb +12 -4
  21. data/lib/redis/distributed.rb +32 -12
  22. data/lib/redis/errors.rb +46 -0
  23. data/lib/redis/hash_ring.rb +20 -64
  24. data/lib/redis/pipeline.rb +9 -7
  25. data/lib/redis/version.rb +1 -1
  26. metadata +53 -196
  27. data/.gitignore +0 -16
  28. data/.travis.yml +0 -89
  29. data/.travis/Gemfile +0 -11
  30. data/.yardopts +0 -3
  31. data/Gemfile +0 -4
  32. data/Rakefile +0 -87
  33. data/benchmarking/logging.rb +0 -71
  34. data/benchmarking/pipeline.rb +0 -51
  35. data/benchmarking/speed.rb +0 -21
  36. data/benchmarking/suite.rb +0 -24
  37. data/benchmarking/worker.rb +0 -71
  38. data/examples/basic.rb +0 -15
  39. data/examples/consistency.rb +0 -114
  40. data/examples/dist_redis.rb +0 -43
  41. data/examples/incr-decr.rb +0 -17
  42. data/examples/list.rb +0 -26
  43. data/examples/pubsub.rb +0 -37
  44. data/examples/sentinel.rb +0 -41
  45. data/examples/sentinel/start +0 -49
  46. data/examples/sets.rb +0 -36
  47. data/examples/unicorn/config.ru +0 -3
  48. data/examples/unicorn/unicorn.rb +0 -20
  49. data/redis.gemspec +0 -44
  50. data/test/bitpos_test.rb +0 -69
  51. data/test/blocking_commands_test.rb +0 -42
  52. data/test/client_test.rb +0 -59
  53. data/test/command_map_test.rb +0 -30
  54. data/test/commands_on_hashes_test.rb +0 -21
  55. data/test/commands_on_hyper_log_log_test.rb +0 -21
  56. data/test/commands_on_lists_test.rb +0 -20
  57. data/test/commands_on_sets_test.rb +0 -77
  58. data/test/commands_on_sorted_sets_test.rb +0 -137
  59. data/test/commands_on_strings_test.rb +0 -101
  60. data/test/commands_on_value_types_test.rb +0 -133
  61. data/test/connection_handling_test.rb +0 -277
  62. data/test/connection_test.rb +0 -57
  63. data/test/distributed_blocking_commands_test.rb +0 -46
  64. data/test/distributed_commands_on_hashes_test.rb +0 -10
  65. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  66. data/test/distributed_commands_on_lists_test.rb +0 -22
  67. data/test/distributed_commands_on_sets_test.rb +0 -83
  68. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  69. data/test/distributed_commands_on_strings_test.rb +0 -59
  70. data/test/distributed_commands_on_value_types_test.rb +0 -95
  71. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  72. data/test/distributed_connection_handling_test.rb +0 -23
  73. data/test/distributed_internals_test.rb +0 -79
  74. data/test/distributed_key_tags_test.rb +0 -52
  75. data/test/distributed_persistence_control_commands_test.rb +0 -26
  76. data/test/distributed_publish_subscribe_test.rb +0 -92
  77. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  78. data/test/distributed_scripting_test.rb +0 -102
  79. data/test/distributed_sorting_test.rb +0 -20
  80. data/test/distributed_test.rb +0 -58
  81. data/test/distributed_transactions_test.rb +0 -32
  82. data/test/encoding_test.rb +0 -18
  83. data/test/error_replies_test.rb +0 -59
  84. data/test/fork_safety_test.rb +0 -65
  85. data/test/helper.rb +0 -232
  86. data/test/helper_test.rb +0 -24
  87. data/test/internals_test.rb +0 -417
  88. data/test/lint/blocking_commands.rb +0 -150
  89. data/test/lint/hashes.rb +0 -162
  90. data/test/lint/hyper_log_log.rb +0 -60
  91. data/test/lint/lists.rb +0 -143
  92. data/test/lint/sets.rb +0 -140
  93. data/test/lint/sorted_sets.rb +0 -316
  94. data/test/lint/strings.rb +0 -260
  95. data/test/lint/value_types.rb +0 -122
  96. data/test/persistence_control_commands_test.rb +0 -26
  97. data/test/pipelining_commands_test.rb +0 -242
  98. data/test/publish_subscribe_test.rb +0 -282
  99. data/test/remote_server_control_commands_test.rb +0 -118
  100. data/test/scanning_test.rb +0 -413
  101. data/test/scripting_test.rb +0 -78
  102. data/test/sentinel_command_test.rb +0 -80
  103. data/test/sentinel_test.rb +0 -255
  104. data/test/sorting_test.rb +0 -59
  105. data/test/ssl_test.rb +0 -73
  106. data/test/support/connection/hiredis.rb +0 -1
  107. data/test/support/connection/ruby.rb +0 -1
  108. data/test/support/connection/synchrony.rb +0 -17
  109. data/test/support/redis_mock.rb +0 -130
  110. data/test/support/ssl/gen_certs.sh +0 -31
  111. data/test/support/ssl/trusted-ca.crt +0 -25
  112. data/test/support/ssl/trusted-ca.key +0 -27
  113. data/test/support/ssl/trusted-cert.crt +0 -81
  114. data/test/support/ssl/trusted-cert.key +0 -28
  115. data/test/support/ssl/untrusted-ca.crt +0 -26
  116. data/test/support/ssl/untrusted-ca.key +0 -27
  117. data/test/support/ssl/untrusted-cert.crt +0 -82
  118. data/test/support/ssl/untrusted-cert.key +0 -28
  119. data/test/support/wire/synchrony.rb +0 -24
  120. data/test/support/wire/thread.rb +0 -5
  121. data/test/synchrony_driver.rb +0 -88
  122. data/test/test.conf.erb +0 -9
  123. data/test/thread_safety_test.rb +0 -62
  124. data/test/transactions_test.rb +0 -264
  125. data/test/unknown_commands_test.rb +0 -14
  126. data/test/url_param_test.rb +0 -138
@@ -1,4 +1,4 @@
1
- require "redis/connection/registry"
1
+ require_relative "connection/registry"
2
2
 
3
3
  # If a connection driver was required before this file, the array
4
4
  # Redis::Connection.drivers will contain one or more classes. The last driver
@@ -6,4 +6,4 @@ require "redis/connection/registry"
6
6
  # the plain Ruby driver as our default. Another driver can be required at a
7
7
  # later point in time, causing it to be the last element of the #drivers array
8
8
  # and therefore be chosen by default.
9
- require "redis/connection/ruby" if Redis::Connection.drivers.empty?
9
+ require_relative "connection/ruby" if Redis::Connection.drivers.empty?
@@ -30,14 +30,8 @@ class Redis
30
30
 
31
31
  protected
32
32
 
33
- if defined?(Encoding::default_external)
34
- def encode(string)
35
- string.force_encoding(Encoding::default_external)
36
- end
37
- else
38
- def encode(string)
39
- string
40
- end
33
+ def encode(string)
34
+ string.force_encoding(Encoding.default_external)
41
35
  end
42
36
  end
43
37
  end
@@ -1,5 +1,5 @@
1
- require "redis/connection/registry"
2
- require "redis/errors"
1
+ require_relative "registry"
2
+ require_relative "../errors"
3
3
  require "hiredis/connection"
4
4
  require "timeout"
5
5
 
@@ -1,6 +1,6 @@
1
- require "redis/connection/registry"
2
- require "redis/connection/command_helper"
3
- require "redis/errors"
1
+ require_relative "registry"
2
+ require_relative "command_helper"
3
+ require_relative "../errors"
4
4
  require "socket"
5
5
  require "timeout"
6
6
 
@@ -10,36 +10,17 @@ rescue LoadError
10
10
  # Not all systems have OpenSSL support
11
11
  end
12
12
 
13
- if RUBY_VERSION < "1.9.3"
14
- class String
15
- # Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
16
- # We can simply slice the string, which is a byte array there.
17
- def byteslice(*args)
18
- slice(*args)
19
- end
20
- end
21
- end
22
-
23
13
  class Redis
24
14
  module Connection
25
15
  module SocketMixin
26
16
 
27
17
  CRLF = "\r\n".freeze
28
18
 
29
- # Exceptions raised during non-blocking I/O ops that require retrying the op
30
- if RUBY_VERSION >= "1.9.3"
31
- NBIO_READ_EXCEPTIONS = [IO::WaitReadable]
32
- NBIO_WRITE_EXCEPTIONS = [IO::WaitWritable]
33
- else
34
- NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
35
- NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
36
- end
37
-
38
19
  def initialize(*args)
39
20
  super(*args)
40
21
 
41
22
  @timeout = @write_timeout = nil
42
- @buffer = ""
23
+ @buffer = "".dup
43
24
  end
44
25
 
45
26
  def timeout=(timeout)
@@ -83,13 +64,13 @@ class Redis
83
64
  begin
84
65
  read_nonblock(nbytes)
85
66
 
86
- rescue *NBIO_READ_EXCEPTIONS
67
+ rescue IO::WaitReadable
87
68
  if IO.select([self], nil, nil, @timeout)
88
69
  retry
89
70
  else
90
71
  raise Redis::TimeoutError
91
72
  end
92
- rescue *NBIO_WRITE_EXCEPTIONS
73
+ rescue IO::WaitWritable
93
74
  if IO.select(nil, [self], nil, @timeout)
94
75
  retry
95
76
  else
@@ -105,13 +86,13 @@ class Redis
105
86
  begin
106
87
  write_nonblock(data)
107
88
 
108
- rescue *NBIO_WRITE_EXCEPTIONS
89
+ rescue IO::WaitWritable
109
90
  if IO.select(nil, [self], nil, @write_timeout)
110
91
  retry
111
92
  else
112
93
  raise Redis::TimeoutError
113
94
  end
114
- rescue *NBIO_READ_EXCEPTIONS
95
+ rescue IO::WaitReadable
115
96
  if IO.select([self], nil, nil, @write_timeout)
116
97
  retry
117
98
  else
@@ -286,7 +267,10 @@ class Redis
286
267
  ssl_sock = new(tcp_sock, ctx)
287
268
  ssl_sock.hostname = host
288
269
  ssl_sock.connect
289
- ssl_sock.post_connection_check(host)
270
+
271
+ unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
272
+ ssl_sock.post_connection_check(host)
273
+ end
290
274
 
291
275
  ssl_sock
292
276
  end
@@ -307,14 +291,13 @@ class Redis
307
291
  raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
308
292
  sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
309
293
  elsif config[:scheme] == "rediss" || config[:ssl]
310
- raise ArgumentError, "This library does not support SSL on Ruby < 1.9" if RUBY_VERSION < "1.9.3"
311
294
  sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
312
295
  else
313
296
  sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
314
297
  end
315
298
 
316
299
  instance = new(sock)
317
- instance.timeout = config[:timeout]
300
+ instance.timeout = config[:read_timeout]
318
301
  instance.write_timeout = config[:write_timeout]
319
302
  instance.set_tcp_keepalive config[:tcp_keepalive]
320
303
  instance
@@ -1,6 +1,6 @@
1
- require "redis/connection/command_helper"
2
- require "redis/connection/registry"
3
- require "redis/errors"
1
+ require_relative "command_helper"
2
+ require_relative "registry"
3
+ require_relative "../errors"
4
4
  require "em-synchrony"
5
5
  require "hiredis/reader"
6
6
 
@@ -72,7 +72,15 @@ class Redis
72
72
 
73
73
  def self.connect(config)
74
74
  if config[:scheme] == "unix"
75
- conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
75
+ begin
76
+ conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
77
+ rescue RuntimeError => e
78
+ if e.message == "no connection"
79
+ raise Errno::ECONNREFUSED
80
+ else
81
+ raise e
82
+ end
83
+ end
76
84
  elsif config[:scheme] == "rediss" || config[:ssl]
77
85
  raise NotImplementedError, "SSL not supported by synchrony driver"
78
86
  else
@@ -1,4 +1,4 @@
1
- require "redis/hash_ring"
1
+ require_relative "hash_ring"
2
2
 
3
3
  class Redis
4
4
  class Distributed
@@ -144,8 +144,8 @@ class Redis
144
144
  end
145
145
 
146
146
  # Create a key using the serialized value, previously obtained using DUMP.
147
- def restore(key, ttl, serialized_value)
148
- node_for(key).restore(key, ttl, serialized_value)
147
+ def restore(key, ttl, serialized_value, options = {})
148
+ node_for(key).restore(key, ttl, serialized_value, options)
149
149
  end
150
150
 
151
151
  # Transfer a key from the connected instance to another instance.
@@ -161,6 +161,14 @@ class Redis
161
161
  end
162
162
  end
163
163
 
164
+ # Unlink keys.
165
+ def unlink(*args)
166
+ keys_per_node = args.group_by { |key| node_for(key) }
167
+ keys_per_node.inject(0) do |sum, (node, keys)|
168
+ sum + node.unlink(*keys)
169
+ end
170
+ end
171
+
164
172
  # Determine if a key exists.
165
173
  def exists(key)
166
174
  node_for(key).exists(key)
@@ -277,13 +285,16 @@ class Redis
277
285
  node_for(key).get(key)
278
286
  end
279
287
 
280
- # Get the values of all the given keys.
288
+ # Get the values of all the given keys as an Array.
281
289
  def mget(*keys)
282
- raise CannotDistribute, :mget
290
+ mapped_mget(*keys).values_at(*keys)
283
291
  end
284
292
 
293
+ # Get the values of all the given keys as a Hash.
285
294
  def mapped_mget(*keys)
286
- raise CannotDistribute, :mapped_mget
295
+ keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
296
+ results.merge! node.mapped_mget(*subkeys)
297
+ end
287
298
  end
288
299
 
289
300
  # Overwrite part of a string at key starting at the specified offset.
@@ -392,12 +403,11 @@ class Redis
392
403
  def _bpop(cmd, args)
393
404
  options = {}
394
405
 
395
- case args.last
396
- when Hash
406
+ if args.last.is_a?(Hash)
397
407
  options = args.pop
398
- when Integer
408
+ elsif args.last.respond_to?(:to_int)
399
409
  # Issue deprecation notice in obnoxious mode...
400
- options[:timeout] = args.pop
410
+ options[:timeout] = args.pop.to_int
401
411
  end
402
412
 
403
413
  if args.size > 1
@@ -509,6 +519,16 @@ class Redis
509
519
  node_for(key).smembers(key)
510
520
  end
511
521
 
522
+ # Scan a set
523
+ def sscan(key, cursor, options={})
524
+ node_for(key).sscan(key, cursor, options)
525
+ end
526
+
527
+ # Scan a set and return an enumerator
528
+ def sscan_each(key, options={}, &block)
529
+ node_for(key).sscan_each(key, options, &block)
530
+ end
531
+
512
532
  # Subtract multiple sets.
513
533
  def sdiff(*keys)
514
534
  ensure_same_node(:sdiff, keys) do |node|
@@ -679,8 +699,8 @@ class Redis
679
699
  end
680
700
 
681
701
  # Delete one or more hash fields.
682
- def hdel(key, field)
683
- node_for(key).hdel(key, field)
702
+ def hdel(key, *fields)
703
+ node_for(key).hdel(key, *fields)
684
704
  end
685
705
 
686
706
  # Determine if a hash field exists.
@@ -37,4 +37,50 @@ class Redis
37
37
  # Raised when the connection was inherited by a child process.
38
38
  class InheritedError < BaseConnectionError
39
39
  end
40
+
41
+ # Raised when client options are invalid.
42
+ class InvalidClientOptionError < BaseError
43
+ end
44
+
45
+ class Cluster
46
+ # Raised when client connected to redis as cluster mode
47
+ # and some cluster subcommands were called.
48
+ class OrchestrationCommandNotSupported < BaseError
49
+ def initialize(command, subcommand = '')
50
+ str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
51
+ msg = "#{str} command should be used with care "\
52
+ 'only by applications orchestrating Redis Cluster, like redis-trib, '\
53
+ 'and the command if used out of the right context can leave the cluster '\
54
+ 'in a wrong state or cause data loss.'
55
+ super(msg)
56
+ end
57
+ end
58
+
59
+ # Raised when error occurs on any node of cluster.
60
+ class CommandErrorCollection < BaseError
61
+ attr_reader :errors
62
+
63
+ # @param errors [Hash{String => Redis::CommandError}]
64
+ # @param error_message [String]
65
+ def initialize(errors, error_message = 'Command errors were replied on any node')
66
+ @errors = errors
67
+ super(error_message)
68
+ end
69
+ end
70
+
71
+ # Raised when cluster client can't select node.
72
+ class AmbiguousNodeError < BaseError
73
+ def initialize(command)
74
+ super("Cluster client doesn't know which node the #{command} command should be sent to.")
75
+ end
76
+ end
77
+
78
+ # Raised when commands in pipelining include cross slot keys.
79
+ class CrossSlotPipeliningError < BaseError
80
+ def initialize(keys)
81
+ super("Cluster client couldn't send pipelining to single node. "\
82
+ "The commands include cross slot keys. #{keys}")
83
+ end
84
+ end
85
+ end
40
86
  end
@@ -25,7 +25,6 @@ class Redis
25
25
  @nodes << node
26
26
  @replicas.times do |i|
27
27
  key = Zlib.crc32("#{node.id}:#{i}")
28
- raise "Node ID collision" if @ring.has_key?(key)
29
28
  @ring[key] = node
30
29
  @sorted_keys << key
31
30
  end
@@ -61,72 +60,29 @@ class Redis
61
60
  end
62
61
  end
63
62
 
64
- class << self
65
-
66
- # gem install RubyInline to use this code
67
- # Native extension to perform the binary search within the hashring.
68
- # There's a pure ruby version below so this is purely optional
69
- # for performance. In testing 20k gets and sets, the native
70
- # binary search shaved about 12% off the runtime (9sec -> 8sec).
71
- begin
72
- require 'inline'
73
- inline do |builder|
74
- builder.c <<-EOM
75
- int binary_search(VALUE ary, unsigned int r) {
76
- int upper = RARRAY_LEN(ary) - 1;
77
- int lower = 0;
78
- int idx = 0;
79
-
80
- while (lower <= upper) {
81
- idx = (lower + upper) / 2;
82
-
83
- VALUE continuumValue = RARRAY_PTR(ary)[idx];
84
- unsigned int l = NUM2UINT(continuumValue);
85
- if (l == r) {
86
- return idx;
87
- }
88
- else if (l > r) {
89
- upper = idx - 1;
90
- }
91
- else {
92
- lower = idx + 1;
93
- }
94
- }
95
- if (upper < 0) {
96
- upper = RARRAY_LEN(ary) - 1;
97
- }
98
- return upper;
99
- }
100
- EOM
101
- end
102
- rescue Exception
103
- # Find the closest index in HashRing with value <= the given value
104
- def binary_search(ary, value, &block)
105
- upper = ary.size - 1
106
- lower = 0
107
- idx = 0
108
-
109
- while(lower <= upper) do
110
- idx = (lower + upper) / 2
111
- comp = ary[idx] <=> value
112
-
113
- if comp == 0
114
- return idx
115
- elsif comp > 0
116
- upper = idx - 1
117
- else
118
- lower = idx + 1
119
- end
120
- end
121
-
122
- if upper < 0
123
- upper = ary.size - 1
124
- end
125
- return upper
63
+ # Find the closest index in HashRing with value <= the given value
64
+ def self.binary_search(ary, value, &block)
65
+ upper = ary.size - 1
66
+ lower = 0
67
+ idx = 0
68
+
69
+ while(lower <= upper) do
70
+ idx = (lower + upper) / 2
71
+ comp = ary[idx] <=> value
72
+
73
+ if comp == 0
74
+ return idx
75
+ elsif comp > 0
76
+ upper = idx - 1
77
+ else
78
+ lower = idx + 1
126
79
  end
80
+ end
127
81
 
82
+ if upper < 0
83
+ upper = ary.size - 1
128
84
  end
85
+ return upper
129
86
  end
130
-
131
87
  end
132
88
  end
@@ -1,10 +1,4 @@
1
1
  class Redis
2
- unless defined?(::BasicObject)
3
- class BasicObject
4
- instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
5
- end
6
- end
7
-
8
2
  class Pipeline
9
3
  attr_accessor :db
10
4
 
@@ -28,6 +22,10 @@ class Redis
28
22
  @shutdown
29
23
  end
30
24
 
25
+ def empty?
26
+ @futures.empty?
27
+ end
28
+
31
29
  def call(command, &block)
32
30
  # A pipeline that contains a shutdown should not raise ECONNRESET when
33
31
  # the connection is gone.
@@ -92,7 +90,11 @@ class Redis
92
90
  end
93
91
 
94
92
  def commands
95
- [[:multi]] + super + [[:exec]]
93
+ if empty?
94
+ []
95
+ else
96
+ [[:multi]] + super + [[:exec]]
97
+ end
96
98
  end
97
99
  end
98
100
  end