redis 3.3.5 → 4.1.0

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.
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