redis 3.2.0 → 4.6.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 (133) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +278 -15
  3. data/README.md +260 -76
  4. data/lib/redis/client.rb +239 -115
  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 +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -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 +804 -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 +92 -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 +11 -6
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +173 -64
  36. data/lib/redis/connection/synchrony.rb +32 -8
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +233 -74
  39. data/lib/redis/errors.rb +48 -0
  40. data/lib/redis/hash_ring.rb +30 -72
  41. data/lib/redis/pipeline.rb +145 -12
  42. data/lib/redis/subscribe.rb +20 -13
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +171 -2476
  45. metadata +71 -165
  46. data/.gitignore +0 -15
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -54
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -68
  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 -43
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/command_map_test.rb +0 -30
  73. data/test/commands_on_hashes_test.rb +0 -21
  74. data/test/commands_on_hyper_log_log_test.rb +0 -21
  75. data/test/commands_on_lists_test.rb +0 -20
  76. data/test/commands_on_sets_test.rb +0 -77
  77. data/test/commands_on_sorted_sets_test.rb +0 -123
  78. data/test/commands_on_strings_test.rb +0 -101
  79. data/test/commands_on_value_types_test.rb +0 -131
  80. data/test/connection_handling_test.rb +0 -189
  81. data/test/db/.gitkeep +0 -0
  82. data/test/distributed_blocking_commands_test.rb +0 -46
  83. data/test/distributed_commands_on_hashes_test.rb +0 -10
  84. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  85. data/test/distributed_commands_on_lists_test.rb +0 -22
  86. data/test/distributed_commands_on_sets_test.rb +0 -83
  87. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  88. data/test/distributed_commands_on_strings_test.rb +0 -59
  89. data/test/distributed_commands_on_value_types_test.rb +0 -95
  90. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  91. data/test/distributed_connection_handling_test.rb +0 -23
  92. data/test/distributed_internals_test.rb +0 -70
  93. data/test/distributed_key_tags_test.rb +0 -52
  94. data/test/distributed_persistence_control_commands_test.rb +0 -26
  95. data/test/distributed_publish_subscribe_test.rb +0 -92
  96. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  97. data/test/distributed_scripting_test.rb +0 -102
  98. data/test/distributed_sorting_test.rb +0 -20
  99. data/test/distributed_test.rb +0 -58
  100. data/test/distributed_transactions_test.rb +0 -32
  101. data/test/encoding_test.rb +0 -18
  102. data/test/error_replies_test.rb +0 -59
  103. data/test/fork_safety_test.rb +0 -65
  104. data/test/helper.rb +0 -232
  105. data/test/helper_test.rb +0 -24
  106. data/test/internals_test.rb +0 -434
  107. data/test/lint/blocking_commands.rb +0 -150
  108. data/test/lint/hashes.rb +0 -162
  109. data/test/lint/hyper_log_log.rb +0 -60
  110. data/test/lint/lists.rb +0 -143
  111. data/test/lint/sets.rb +0 -125
  112. data/test/lint/sorted_sets.rb +0 -238
  113. data/test/lint/strings.rb +0 -260
  114. data/test/lint/value_types.rb +0 -122
  115. data/test/persistence_control_commands_test.rb +0 -26
  116. data/test/pipelining_commands_test.rb +0 -242
  117. data/test/publish_subscribe_test.rb +0 -210
  118. data/test/remote_server_control_commands_test.rb +0 -117
  119. data/test/scanning_test.rb +0 -413
  120. data/test/scripting_test.rb +0 -78
  121. data/test/sorting_test.rb +0 -59
  122. data/test/support/connection/hiredis.rb +0 -1
  123. data/test/support/connection/ruby.rb +0 -1
  124. data/test/support/connection/synchrony.rb +0 -17
  125. data/test/support/redis_mock.rb +0 -115
  126. data/test/support/wire/synchrony.rb +0 -24
  127. data/test/support/wire/thread.rb +0 -5
  128. data/test/synchrony_driver.rb +0 -88
  129. data/test/test.conf +0 -9
  130. data/test/thread_safety_test.rb +0 -32
  131. data/test/transactions_test.rb +0 -264
  132. data/test/unknown_commands_test.rb +0 -14
  133. data/test/url_param_test.rb +0 -132
@@ -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 = []
@@ -32,11 +33,11 @@ class Redis
32
33
  end
33
34
 
34
35
  def remove_node(node)
35
- @nodes.reject!{|n| n.id == node.id}
36
+ @nodes.reject! { |n| n.id == node.id }
36
37
  @replicas.times do |i|
37
38
  key = Zlib.crc32("#{node.id}:#{i}")
38
39
  @ring.delete(key)
39
- @sorted_keys.reject! {|k| k == key}
40
+ @sorted_keys.reject! { |k| k == key }
40
41
  end
41
42
  end
42
43
 
@@ -46,86 +47,43 @@ class Redis
46
47
  end
47
48
 
48
49
  def get_node_pos(key)
49
- return [nil,nil] if @ring.size == 0
50
+ return [nil, nil] if @ring.empty?
51
+
50
52
  crc = Zlib.crc32(key)
51
53
  idx = HashRing.binary_search(@sorted_keys, crc)
52
- return [@ring[@sorted_keys[idx]], idx]
54
+ [@ring[@sorted_keys[idx]], idx]
53
55
  end
54
56
 
55
57
  def iter_nodes(key)
56
- return [nil,nil] if @ring.size == 0
58
+ return [nil, nil] if @ring.empty?
59
+
57
60
  _, pos = get_node_pos(key)
58
61
  @ring.size.times do |n|
59
- yield @ring[@sorted_keys[(pos+n) % @ring.size]]
62
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
60
63
  end
61
64
  end
62
65
 
63
- class << self
64
-
65
- # gem install RubyInline to use this code
66
- # Native extension to perform the binary search within the hashring.
67
- # There's a pure ruby version below so this is purely optional
68
- # for performance. In testing 20k gets and sets, the native
69
- # binary search shaved about 12% off the runtime (9sec -> 8sec).
70
- begin
71
- require 'inline'
72
- inline do |builder|
73
- builder.c <<-EOM
74
- int binary_search(VALUE ary, unsigned int r) {
75
- int upper = RARRAY_LEN(ary) - 1;
76
- int lower = 0;
77
- int idx = 0;
78
-
79
- while (lower <= upper) {
80
- idx = (lower + upper) / 2;
81
-
82
- VALUE continuumValue = RARRAY_PTR(ary)[idx];
83
- unsigned int l = NUM2UINT(continuumValue);
84
- if (l == r) {
85
- return idx;
86
- }
87
- else if (l > r) {
88
- upper = idx - 1;
89
- }
90
- else {
91
- lower = idx + 1;
92
- }
93
- }
94
- if (upper < 0) {
95
- upper = RARRAY_LEN(ary) - 1;
96
- }
97
- return upper;
98
- }
99
- 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
100
82
  end
101
- rescue Exception
102
- # Find the closest index in HashRing with value <= the given value
103
- def binary_search(ary, value, &block)
104
- upper = ary.size - 1
105
- lower = 0
106
- idx = 0
107
-
108
- while(lower <= upper) do
109
- idx = (lower + upper) / 2
110
- comp = ary[idx] <=> value
111
-
112
- if comp == 0
113
- return idx
114
- elsif comp > 0
115
- upper = idx - 1
116
- else
117
- lower = idx + 1
118
- end
119
- end
120
-
121
- if upper < 0
122
- upper = ary.size - 1
123
- end
124
- return upper
125
- end
126
-
127
83
  end
128
- end
129
84
 
85
+ upper = ary.size - 1 if upper < 0
86
+ upper
87
+ end
130
88
  end
131
89
  end
@@ -1,21 +1,86 @@
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
+ private
26
+
27
+ def synchronize
28
+ yield self
29
+ end
30
+
31
+ def send_command(command, &block)
32
+ @pipeline.call(command, &block)
33
+ end
34
+
35
+ def send_blocking_command(command, timeout, &block)
36
+ @pipeline.call_with_timeout(command, timeout, &block)
5
37
  end
6
38
  end
7
39
 
8
40
  class Pipeline
41
+ REDIS_INTERNAL_PATH = File.expand_path("..", __dir__).freeze
42
+ # Redis use MonitorMixin#synchronize and this class use DelegateClass which we want to filter out.
43
+ # Both are in the stdlib so we can simply filter the entire stdlib out.
44
+ STDLIB_PATH = File.expand_path("..", MonitorMixin.instance_method(:synchronize).source_location.first).freeze
45
+
46
+ class << self
47
+ def deprecation_warning(method, caller_locations) # :nodoc:
48
+ callsite = caller_locations.find { |l| !l.path.start_with?(REDIS_INTERNAL_PATH, STDLIB_PATH) }
49
+ callsite ||= caller_locations.last # The caller_locations should be large enough, but just in case.
50
+ ::Redis.deprecate! <<~MESSAGE
51
+ Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.
52
+
53
+ redis.#{method} do
54
+ redis.get("key")
55
+ end
56
+
57
+ should be replaced by
58
+
59
+ redis.#{method} do |pipeline|
60
+ pipeline.get("key")
61
+ end
62
+
63
+ (called from #{callsite}}
64
+ MESSAGE
65
+ end
66
+ end
67
+
9
68
  attr_accessor :db
69
+ attr_reader :client
10
70
 
11
71
  attr :futures
12
72
 
13
- def initialize
73
+ def initialize(client)
74
+ @client = client.is_a?(Pipeline) ? client.client : client
14
75
  @with_reconnect = true
15
76
  @shutdown = false
16
77
  @futures = []
17
78
  end
18
79
 
80
+ def timeout
81
+ client.timeout
82
+ end
83
+
19
84
  def with_reconnect?
20
85
  @with_reconnect
21
86
  end
@@ -28,15 +93,23 @@ class Redis
28
93
  @shutdown
29
94
  end
30
95
 
31
- def call(command, &block)
96
+ def empty?
97
+ @futures.empty?
98
+ end
99
+
100
+ def call(command, timeout: nil, &block)
32
101
  # A pipeline that contains a shutdown should not raise ECONNRESET when
33
102
  # the connection is gone.
34
103
  @shutdown = true if command.first == :shutdown
35
- future = Future.new(command, block)
104
+ future = Future.new(command, block, timeout)
36
105
  @futures << future
37
106
  future
38
107
  end
39
108
 
109
+ def call_with_timeout(command, timeout, &block)
110
+ call(command, timeout: timeout, &block)
111
+ end
112
+
40
113
  def call_pipeline(pipeline)
41
114
  @shutdown = true if pipeline.shutdown?
42
115
  @futures.concat(pipeline.futures)
@@ -45,10 +118,14 @@ class Redis
45
118
  end
46
119
 
47
120
  def commands
48
- @futures.map { |f| f._command }
121
+ @futures.map(&:_command)
49
122
  end
50
123
 
51
- def with_reconnect(val=true)
124
+ def timeouts
125
+ @futures.map(&:timeout)
126
+ end
127
+
128
+ def with_reconnect(val = true)
52
129
  @with_reconnect = false unless val
53
130
  yield
54
131
  end
@@ -80,7 +157,8 @@ class Redis
80
157
 
81
158
  if exec.size < futures.size
82
159
  # Some command wasn't recognized by Redis.
83
- raise replies.detect { |r| r.is_a?(CommandError) }
160
+ command_error = replies.detect { |r| r.is_a?(CommandError) }
161
+ raise command_error
84
162
  end
85
163
 
86
164
  super(exec) do |reply|
@@ -91,12 +169,54 @@ class Redis
91
169
  end
92
170
  end
93
171
 
172
+ def timeouts
173
+ if empty?
174
+ []
175
+ else
176
+ [nil, *super, nil]
177
+ end
178
+ end
179
+
94
180
  def commands
95
- [[:multi]] + super + [[:exec]]
181
+ if empty?
182
+ []
183
+ else
184
+ [[:multi]] + super + [[:exec]]
185
+ end
96
186
  end
97
187
  end
98
188
  end
99
189
 
190
+ class DeprecatedPipeline < DelegateClass(Pipeline)
191
+ def initialize(pipeline)
192
+ super(pipeline)
193
+ @deprecation_displayed = false
194
+ end
195
+
196
+ def __getobj__
197
+ unless @deprecation_displayed
198
+ Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 10))
199
+ @deprecation_displayed = true
200
+ end
201
+ @delegate_dc_obj
202
+ end
203
+ end
204
+
205
+ class DeprecatedMulti < DelegateClass(Pipeline::Multi)
206
+ def initialize(pipeline)
207
+ super(pipeline)
208
+ @deprecation_displayed = false
209
+ end
210
+
211
+ def __getobj__
212
+ unless @deprecation_displayed
213
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 10))
214
+ @deprecation_displayed = true
215
+ end
216
+ @delegate_dc_obj
217
+ end
218
+ end
219
+
100
220
  class FutureNotReady < RuntimeError
101
221
  def initialize
102
222
  super("Value will be available once the pipeline executes.")
@@ -106,12 +226,25 @@ class Redis
106
226
  class Future < BasicObject
107
227
  FutureNotReady = ::Redis::FutureNotReady.new
108
228
 
109
- def initialize(command, transformation)
229
+ attr_reader :timeout
230
+
231
+ def initialize(command, transformation, timeout)
110
232
  @command = command
111
233
  @transformation = transformation
234
+ @timeout = timeout
112
235
  @object = FutureNotReady
113
236
  end
114
237
 
238
+ def ==(_other)
239
+ message = +"The methods == and != are deprecated for Redis::Future and will be removed in 5.0.0"
240
+ message << " - You probably meant to call .value == or .value !="
241
+ message << " (#{::Kernel.caller(1, 1).first})\n"
242
+
243
+ ::Redis.deprecate!(message)
244
+
245
+ super
246
+ end
247
+
115
248
  def inspect
116
249
  "<Redis::Future #{@command.inspect}>"
117
250
  end
@@ -126,7 +259,7 @@ class Redis
126
259
  end
127
260
 
128
261
  def value
129
- ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
262
+ ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
130
263
  @object
131
264
  end
132
265
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  class SubscribedClient
3
5
  def initialize(client)
@@ -12,10 +14,18 @@ class Redis
12
14
  subscription("subscribe", "unsubscribe", channels, block)
13
15
  end
14
16
 
17
+ def subscribe_with_timeout(timeout, *channels, &block)
18
+ subscription("subscribe", "unsubscribe", channels, block, timeout)
19
+ end
20
+
15
21
  def psubscribe(*channels, &block)
16
22
  subscription("psubscribe", "punsubscribe", channels, block)
17
23
  end
18
24
 
25
+ def psubscribe_with_timeout(timeout, *channels, &block)
26
+ subscription("psubscribe", "punsubscribe", channels, block, timeout)
27
+ end
28
+
19
29
  def unsubscribe(*channels)
20
30
  call([:unsubscribe, *channels])
21
31
  end
@@ -24,24 +34,21 @@ class Redis
24
34
  call([:punsubscribe, *channels])
25
35
  end
26
36
 
27
- protected
37
+ protected
28
38
 
29
- def subscription(start, stop, channels, block)
39
+ def subscription(start, stop, channels, block, timeout = 0)
30
40
  sub = Subscription.new(&block)
31
41
 
32
42
  unsubscribed = false
33
43
 
34
- begin
35
- @client.call_loop([start, *channels]) do |line|
36
- type, *rest = line
37
- sub.callbacks[type].call(*rest)
38
- unsubscribed = type == stop && rest.last == 0
39
- break if unsubscribed
40
- end
41
- ensure
42
- # No need to unsubscribe here. The real client closes the connection
43
- # 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
44
49
  end
50
+ # No need to unsubscribe here. The real client closes the connection
51
+ # whenever an exception is raised (see #ensure_connected).
45
52
  end
46
53
  end
47
54
 
@@ -50,7 +57,7 @@ class Redis
50
57
 
51
58
  def initialize
52
59
  @callbacks = Hash.new do |hash, key|
53
- hash[key] = lambda { |*_| }
60
+ hash[key] = ->(*_) {}
54
61
  end
55
62
 
56
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.2.0"
4
+ VERSION = '4.6.0'
3
5
  end