redis 3.3.5 → 4.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +225 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +177 -100
  5. data/lib/redis/cluster/command.rb +79 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +120 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +34 -0
  11. data/lib/redis/cluster/option.rb +100 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +46 -0
  14. data/lib/redis/cluster.rb +315 -0
  15. data/lib/redis/commands/bitmaps.rb +63 -0
  16. data/lib/redis/commands/cluster.rb +45 -0
  17. data/lib/redis/commands/connection.rb +58 -0
  18. data/lib/redis/commands/geo.rb +84 -0
  19. data/lib/redis/commands/hashes.rb +251 -0
  20. data/lib/redis/commands/hyper_log_log.rb +37 -0
  21. data/lib/redis/commands/keys.rb +411 -0
  22. data/lib/redis/commands/lists.rb +289 -0
  23. data/lib/redis/commands/pubsub.rb +72 -0
  24. data/lib/redis/commands/scripting.rb +114 -0
  25. data/lib/redis/commands/server.rb +188 -0
  26. data/lib/redis/commands/sets.rb +207 -0
  27. data/lib/redis/commands/sorted_sets.rb +812 -0
  28. data/lib/redis/commands/streams.rb +382 -0
  29. data/lib/redis/commands/strings.rb +313 -0
  30. data/lib/redis/commands/transactions.rb +139 -0
  31. data/lib/redis/commands.rb +242 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +136 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +231 -72
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +30 -73
  41. data/lib/redis/pipeline.rb +178 -13
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +173 -2661
  45. metadata +66 -202
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -89
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -87
  52. data/benchmarking/logging.rb +0 -71
  53. data/benchmarking/pipeline.rb +0 -51
  54. data/benchmarking/speed.rb +0 -21
  55. data/benchmarking/suite.rb +0 -24
  56. data/benchmarking/worker.rb +0 -71
  57. data/examples/basic.rb +0 -15
  58. data/examples/consistency.rb +0 -114
  59. data/examples/dist_redis.rb +0 -43
  60. data/examples/incr-decr.rb +0 -17
  61. data/examples/list.rb +0 -26
  62. data/examples/pubsub.rb +0 -37
  63. data/examples/sentinel/sentinel.conf +0 -9
  64. data/examples/sentinel/start +0 -49
  65. data/examples/sentinel.rb +0 -41
  66. data/examples/sets.rb +0 -36
  67. data/examples/unicorn/config.ru +0 -3
  68. data/examples/unicorn/unicorn.rb +0 -20
  69. data/redis.gemspec +0 -44
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/client_test.rb +0 -59
  73. data/test/command_map_test.rb +0 -30
  74. data/test/commands_on_hashes_test.rb +0 -21
  75. data/test/commands_on_hyper_log_log_test.rb +0 -21
  76. data/test/commands_on_lists_test.rb +0 -20
  77. data/test/commands_on_sets_test.rb +0 -77
  78. data/test/commands_on_sorted_sets_test.rb +0 -137
  79. data/test/commands_on_strings_test.rb +0 -101
  80. data/test/commands_on_value_types_test.rb +0 -133
  81. data/test/connection_handling_test.rb +0 -277
  82. data/test/connection_test.rb +0 -57
  83. data/test/db/.gitkeep +0 -0
  84. data/test/distributed_blocking_commands_test.rb +0 -46
  85. data/test/distributed_commands_on_hashes_test.rb +0 -10
  86. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  87. data/test/distributed_commands_on_lists_test.rb +0 -22
  88. data/test/distributed_commands_on_sets_test.rb +0 -83
  89. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  90. data/test/distributed_commands_on_strings_test.rb +0 -59
  91. data/test/distributed_commands_on_value_types_test.rb +0 -95
  92. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  93. data/test/distributed_connection_handling_test.rb +0 -23
  94. data/test/distributed_internals_test.rb +0 -79
  95. data/test/distributed_key_tags_test.rb +0 -52
  96. data/test/distributed_persistence_control_commands_test.rb +0 -26
  97. data/test/distributed_publish_subscribe_test.rb +0 -92
  98. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  99. data/test/distributed_scripting_test.rb +0 -102
  100. data/test/distributed_sorting_test.rb +0 -20
  101. data/test/distributed_test.rb +0 -58
  102. data/test/distributed_transactions_test.rb +0 -32
  103. data/test/encoding_test.rb +0 -18
  104. data/test/error_replies_test.rb +0 -59
  105. data/test/fork_safety_test.rb +0 -65
  106. data/test/helper.rb +0 -232
  107. data/test/helper_test.rb +0 -24
  108. data/test/internals_test.rb +0 -417
  109. data/test/lint/blocking_commands.rb +0 -150
  110. data/test/lint/hashes.rb +0 -162
  111. data/test/lint/hyper_log_log.rb +0 -60
  112. data/test/lint/lists.rb +0 -143
  113. data/test/lint/sets.rb +0 -140
  114. data/test/lint/sorted_sets.rb +0 -316
  115. data/test/lint/strings.rb +0 -260
  116. data/test/lint/value_types.rb +0 -122
  117. data/test/persistence_control_commands_test.rb +0 -26
  118. data/test/pipelining_commands_test.rb +0 -242
  119. data/test/publish_subscribe_test.rb +0 -282
  120. data/test/remote_server_control_commands_test.rb +0 -118
  121. data/test/scanning_test.rb +0 -413
  122. data/test/scripting_test.rb +0 -78
  123. data/test/sentinel_command_test.rb +0 -80
  124. data/test/sentinel_test.rb +0 -255
  125. data/test/sorting_test.rb +0 -59
  126. data/test/ssl_test.rb +0 -73
  127. data/test/support/connection/hiredis.rb +0 -1
  128. data/test/support/connection/ruby.rb +0 -1
  129. data/test/support/connection/synchrony.rb +0 -17
  130. data/test/support/redis_mock.rb +0 -130
  131. data/test/support/ssl/gen_certs.sh +0 -31
  132. data/test/support/ssl/trusted-ca.crt +0 -25
  133. data/test/support/ssl/trusted-ca.key +0 -27
  134. data/test/support/ssl/trusted-cert.crt +0 -81
  135. data/test/support/ssl/trusted-cert.key +0 -28
  136. data/test/support/ssl/untrusted-ca.crt +0 -26
  137. data/test/support/ssl/untrusted-ca.key +0 -27
  138. data/test/support/ssl/untrusted-cert.crt +0 -82
  139. data/test/support/ssl/untrusted-cert.key +0 -28
  140. data/test/support/wire/synchrony.rb +0 -24
  141. data/test/support/wire/thread.rb +0 -5
  142. data/test/synchrony_driver.rb +0 -88
  143. data/test/test.conf.erb +0 -9
  144. data/test/thread_safety_test.rb +0 -62
  145. data/test/transactions_test.rb +0 -264
  146. data/test/unknown_commands_test.rb +0 -14
  147. data/test/url_param_test.rb +0 -138
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  class Redis
4
6
  class HashRing
5
-
6
7
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
8
 
8
9
  attr_reader :ring, :sorted_keys, :replicas, :nodes
@@ -10,7 +11,7 @@ class Redis
10
11
  # nodes is a list of objects that have a proper to_s representation.
11
12
  # replicas indicates how many virtual points should be used pr. node,
12
13
  # replicas are required to improve the distribution.
13
- def initialize(nodes=[], replicas=POINTS_PER_SERVER)
14
+ def initialize(nodes = [], replicas = POINTS_PER_SERVER)
14
15
  @replicas = replicas
15
16
  @ring = {}
16
17
  @nodes = []
@@ -25,7 +26,6 @@ class Redis
25
26
  @nodes << node
26
27
  @replicas.times do |i|
27
28
  key = Zlib.crc32("#{node.id}:#{i}")
28
- raise "Node ID collision" if @ring.has_key?(key)
29
29
  @ring[key] = node
30
30
  @sorted_keys << key
31
31
  end
@@ -33,11 +33,11 @@ class Redis
33
33
  end
34
34
 
35
35
  def remove_node(node)
36
- @nodes.reject!{|n| n.id == node.id}
36
+ @nodes.reject! { |n| n.id == node.id }
37
37
  @replicas.times do |i|
38
38
  key = Zlib.crc32("#{node.id}:#{i}")
39
39
  @ring.delete(key)
40
- @sorted_keys.reject! {|k| k == key}
40
+ @sorted_keys.reject! { |k| k == key }
41
41
  end
42
42
  end
43
43
 
@@ -47,86 +47,43 @@ class Redis
47
47
  end
48
48
 
49
49
  def get_node_pos(key)
50
- return [nil,nil] if @ring.size == 0
50
+ return [nil, nil] if @ring.empty?
51
+
51
52
  crc = Zlib.crc32(key)
52
53
  idx = HashRing.binary_search(@sorted_keys, crc)
53
- return [@ring[@sorted_keys[idx]], idx]
54
+ [@ring[@sorted_keys[idx]], idx]
54
55
  end
55
56
 
56
57
  def iter_nodes(key)
57
- return [nil,nil] if @ring.size == 0
58
+ return [nil, nil] if @ring.empty?
59
+
58
60
  _, pos = get_node_pos(key)
59
61
  @ring.size.times do |n|
60
- yield @ring[@sorted_keys[(pos+n) % @ring.size]]
62
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
61
63
  end
62
64
  end
63
65
 
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
66
+ # Find the closest index in HashRing with value <= the given value
67
+ def self.binary_search(ary, value)
68
+ upper = ary.size - 1
69
+ lower = 0
70
+ idx = 0
71
+
72
+ while lower <= upper
73
+ idx = (lower + upper) / 2
74
+ comp = ary[idx] <=> value
75
+
76
+ if comp == 0
77
+ return idx
78
+ elsif comp > 0
79
+ upper = idx - 1
80
+ else
81
+ lower = idx + 1
101
82
  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
126
- end
127
-
128
83
  end
129
- end
130
84
 
85
+ upper = ary.size - 1 if upper < 0
86
+ upper
87
+ end
131
88
  end
132
89
  end
@@ -1,21 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
1
5
  class Redis
2
- unless defined?(::BasicObject)
3
- class BasicObject
4
- instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
6
+ class PipelinedConnection
7
+ def initialize(pipeline)
8
+ @pipeline = pipeline
9
+ end
10
+
11
+ include Commands
12
+
13
+ def db
14
+ @pipeline.db
15
+ end
16
+
17
+ def db=(db)
18
+ @pipeline.db = db
19
+ end
20
+
21
+ def pipelined
22
+ yield self
23
+ end
24
+
25
+ def call_pipeline(pipeline)
26
+ @pipeline.call_pipeline(pipeline)
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ def synchronize
33
+ yield self
34
+ end
35
+
36
+ def send_command(command, &block)
37
+ @pipeline.call(command, &block)
38
+ end
39
+
40
+ def send_blocking_command(command, timeout, &block)
41
+ @pipeline.call_with_timeout(command, timeout, &block)
5
42
  end
6
43
  end
7
44
 
8
45
  class Pipeline
46
+ REDIS_INTERNAL_PATH = File.expand_path("..", __dir__).freeze
47
+ # Redis use MonitorMixin#synchronize and this class use DelegateClass which we want to filter out.
48
+ # Both are in the stdlib so we can simply filter the entire stdlib out.
49
+ STDLIB_PATH = File.expand_path("..", MonitorMixin.instance_method(:synchronize).source_location.first).freeze
50
+
51
+ class << self
52
+ def deprecation_warning(method, caller_locations) # :nodoc:
53
+ callsite = caller_locations.find { |l| !l.path.start_with?(REDIS_INTERNAL_PATH, STDLIB_PATH) }
54
+ callsite ||= caller_locations.last # The caller_locations should be large enough, but just in case.
55
+ ::Redis.deprecate! <<~MESSAGE
56
+ Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.
57
+
58
+ redis.#{method} do
59
+ redis.get("key")
60
+ end
61
+
62
+ should be replaced by
63
+
64
+ redis.#{method} do |pipeline|
65
+ pipeline.get("key")
66
+ end
67
+
68
+ (called from #{callsite}}
69
+ MESSAGE
70
+ end
71
+ end
72
+
9
73
  attr_accessor :db
74
+ attr_reader :client
10
75
 
11
76
  attr :futures
77
+ alias materialized_futures futures
12
78
 
13
- def initialize
79
+ def initialize(client)
80
+ @client = client.is_a?(Pipeline) ? client.client : client
14
81
  @with_reconnect = true
15
82
  @shutdown = false
16
83
  @futures = []
17
84
  end
18
85
 
86
+ def timeout
87
+ client.timeout
88
+ end
89
+
19
90
  def with_reconnect?
20
91
  @with_reconnect
21
92
  end
@@ -28,27 +99,39 @@ class Redis
28
99
  @shutdown
29
100
  end
30
101
 
31
- def call(command, &block)
102
+ def empty?
103
+ @futures.empty?
104
+ end
105
+
106
+ def call(command, timeout: nil, &block)
32
107
  # A pipeline that contains a shutdown should not raise ECONNRESET when
33
108
  # the connection is gone.
34
109
  @shutdown = true if command.first == :shutdown
35
- future = Future.new(command, block)
110
+ future = Future.new(command, block, timeout)
36
111
  @futures << future
37
112
  future
38
113
  end
39
114
 
115
+ def call_with_timeout(command, timeout, &block)
116
+ call(command, timeout: timeout, &block)
117
+ end
118
+
40
119
  def call_pipeline(pipeline)
41
120
  @shutdown = true if pipeline.shutdown?
42
- @futures.concat(pipeline.futures)
121
+ @futures.concat(pipeline.materialized_futures)
43
122
  @db = pipeline.db
44
123
  nil
45
124
  end
46
125
 
47
126
  def commands
48
- @futures.map { |f| f._command }
127
+ @futures.map(&:_command)
128
+ end
129
+
130
+ def timeouts
131
+ @futures.map(&:timeout)
49
132
  end
50
133
 
51
- def with_reconnect(val=true)
134
+ def with_reconnect(val = true)
52
135
  @with_reconnect = false unless val
53
136
  yield
54
137
  end
@@ -80,7 +163,8 @@ class Redis
80
163
 
81
164
  if exec.size < futures.size
82
165
  # Some command wasn't recognized by Redis.
83
- raise replies.detect { |r| r.is_a?(CommandError) }
166
+ command_error = replies.detect { |r| r.is_a?(CommandError) }
167
+ raise command_error
84
168
  end
85
169
 
86
170
  super(exec) do |reply|
@@ -91,9 +175,63 @@ class Redis
91
175
  end
92
176
  end
93
177
 
178
+ def materialized_futures
179
+ if empty?
180
+ []
181
+ else
182
+ [
183
+ Future.new([:multi], nil, 0),
184
+ *futures,
185
+ MultiFuture.new(futures)
186
+ ]
187
+ end
188
+ end
189
+
190
+ def timeouts
191
+ if empty?
192
+ []
193
+ else
194
+ [nil, *super, nil]
195
+ end
196
+ end
197
+
94
198
  def commands
95
- [[:multi]] + super + [[:exec]]
199
+ if empty?
200
+ []
201
+ else
202
+ [[:multi]] + super + [[:exec]]
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ class DeprecatedPipeline < DelegateClass(Pipeline)
209
+ def initialize(pipeline)
210
+ super(pipeline)
211
+ @deprecation_displayed = false
212
+ end
213
+
214
+ def __getobj__
215
+ unless @deprecation_displayed
216
+ Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 10))
217
+ @deprecation_displayed = true
96
218
  end
219
+ @delegate_dc_obj
220
+ end
221
+ end
222
+
223
+ class DeprecatedMulti < DelegateClass(Pipeline::Multi)
224
+ def initialize(pipeline)
225
+ super(pipeline)
226
+ @deprecation_displayed = false
227
+ end
228
+
229
+ def __getobj__
230
+ unless @deprecation_displayed
231
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 10))
232
+ @deprecation_displayed = true
233
+ end
234
+ @delegate_dc_obj
97
235
  end
98
236
  end
99
237
 
@@ -106,12 +244,25 @@ class Redis
106
244
  class Future < BasicObject
107
245
  FutureNotReady = ::Redis::FutureNotReady.new
108
246
 
109
- def initialize(command, transformation)
247
+ attr_reader :timeout
248
+
249
+ def initialize(command, transformation, timeout)
110
250
  @command = command
111
251
  @transformation = transformation
252
+ @timeout = timeout
112
253
  @object = FutureNotReady
113
254
  end
114
255
 
256
+ def ==(_other)
257
+ message = +"The methods == and != are deprecated for Redis::Future and will be removed in 5.0.0"
258
+ message << " - You probably meant to call .value == or .value !="
259
+ message << " (#{::Kernel.caller(1, 1).first})\n"
260
+
261
+ ::Redis.deprecate!(message)
262
+
263
+ super
264
+ end
265
+
115
266
  def inspect
116
267
  "<Redis::Future #{@command.inspect}>"
117
268
  end
@@ -126,7 +277,7 @@ class Redis
126
277
  end
127
278
 
128
279
  def value
129
- ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
280
+ ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
130
281
  @object
131
282
  end
132
283
 
@@ -138,4 +289,18 @@ class Redis
138
289
  Future
139
290
  end
140
291
  end
292
+
293
+ class MultiFuture < Future
294
+ def initialize(futures)
295
+ @futures = futures
296
+ @command = [:exec]
297
+ end
298
+
299
+ def _set(replies)
300
+ @futures.each_with_index do |future, index|
301
+ future._set(replies[index])
302
+ end
303
+ replies
304
+ end
305
+ end
141
306
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  class SubscribedClient
3
5
  def initialize(client)
@@ -32,24 +34,21 @@ class Redis
32
34
  call([:punsubscribe, *channels])
33
35
  end
34
36
 
35
- protected
37
+ protected
36
38
 
37
39
  def subscription(start, stop, channels, block, timeout = 0)
38
40
  sub = Subscription.new(&block)
39
41
 
40
42
  unsubscribed = false
41
43
 
42
- begin
43
- @client.call_loop([start, *channels], timeout) do |line|
44
- type, *rest = line
45
- sub.callbacks[type].call(*rest)
46
- unsubscribed = type == stop && rest.last == 0
47
- break if unsubscribed
48
- end
49
- ensure
50
- # No need to unsubscribe here. The real client closes the connection
51
- # whenever an exception is raised (see #ensure_connected).
44
+ @client.call_loop([start, *channels], timeout) do |line|
45
+ type, *rest = line
46
+ sub.callbacks[type].call(*rest)
47
+ unsubscribed = type == stop && rest.last == 0
48
+ break if unsubscribed
52
49
  end
50
+ # No need to unsubscribe here. The real client closes the connection
51
+ # whenever an exception is raised (see #ensure_connected).
53
52
  end
54
53
  end
55
54
 
@@ -58,7 +57,7 @@ class Redis
58
57
 
59
58
  def initialize
60
59
  @callbacks = Hash.new do |hash, key|
61
- hash[key] = lambda { |*_| }
60
+ hash[key] = ->(*_) {}
62
61
  end
63
62
 
64
63
  yield(self)
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
- VERSION = "3.3.5"
4
+ VERSION = '4.7.1'
3
5
  end