redis 4.7.1 → 5.0.4

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +75 -161
  4. data/lib/redis/client.rb +92 -608
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +8 -5
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +53 -27
  12. data/lib/redis/commands/lists.rb +19 -23
  13. data/lib/redis/commands/pubsub.rb +7 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +43 -36
  16. data/lib/redis/commands/sorted_sets.rb +18 -12
  17. data/lib/redis/commands/streams.rb +12 -10
  18. data/lib/redis/commands/strings.rb +16 -15
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +1 -8
  21. data/lib/redis/distributed.rb +100 -67
  22. data/lib/redis/errors.rb +14 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +88 -182
  28. metadata +9 -53
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -4,27 +4,30 @@ require "delegate"
4
4
 
5
5
  class Redis
6
6
  class PipelinedConnection
7
- def initialize(pipeline)
7
+ attr_accessor :db
8
+
9
+ def initialize(pipeline, futures = [])
8
10
  @pipeline = pipeline
11
+ @futures = futures
9
12
  end
10
13
 
11
14
  include Commands
12
15
 
13
- def db
14
- @pipeline.db
15
- end
16
-
17
- def db=(db)
18
- @pipeline.db = db
19
- end
20
-
21
16
  def pipelined
22
17
  yield self
23
18
  end
24
19
 
25
- def call_pipeline(pipeline)
26
- @pipeline.call_pipeline(pipeline)
27
- nil
20
+ def multi
21
+ transaction = MultiConnection.new(@pipeline, @futures)
22
+ send_command([:multi])
23
+ size = @futures.size
24
+ yield transaction
25
+ multi_future = MultiFuture.new(@futures[size..-1])
26
+ @pipeline.call_v([:exec]) do |result|
27
+ multi_future._set(result)
28
+ end
29
+ @futures << multi_future
30
+ multi_future
28
31
  end
29
32
 
30
33
  private
@@ -34,204 +37,36 @@ class Redis
34
37
  end
35
38
 
36
39
  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)
42
- end
43
- end
44
-
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
40
+ future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
70
43
  end
71
- end
72
-
73
- attr_accessor :db
74
- attr_reader :client
75
-
76
- attr :futures
77
- alias materialized_futures futures
78
-
79
- def initialize(client)
80
- @client = client.is_a?(Pipeline) ? client.client : client
81
- @with_reconnect = true
82
- @shutdown = false
83
- @futures = []
84
- end
85
-
86
- def timeout
87
- client.timeout
88
- end
89
-
90
- def with_reconnect?
91
- @with_reconnect
92
- end
93
-
94
- def without_reconnect?
95
- !@with_reconnect
96
- end
97
-
98
- def shutdown?
99
- @shutdown
100
- end
101
-
102
- def empty?
103
- @futures.empty?
104
- end
105
-
106
- def call(command, timeout: nil, &block)
107
- # A pipeline that contains a shutdown should not raise ECONNRESET when
108
- # the connection is gone.
109
- @shutdown = true if command.first == :shutdown
110
- future = Future.new(command, block, timeout)
111
44
  @futures << future
112
45
  future
113
46
  end
114
47
 
115
- def call_with_timeout(command, timeout, &block)
116
- call(command, timeout: timeout, &block)
117
- end
118
-
119
- def call_pipeline(pipeline)
120
- @shutdown = true if pipeline.shutdown?
121
- @futures.concat(pipeline.materialized_futures)
122
- @db = pipeline.db
123
- nil
124
- end
125
-
126
- def commands
127
- @futures.map(&:_command)
128
- end
129
-
130
- def timeouts
131
- @futures.map(&:timeout)
132
- end
133
-
134
- def with_reconnect(val = true)
135
- @with_reconnect = false unless val
136
- yield
137
- end
138
-
139
- def without_reconnect(&blk)
140
- with_reconnect(false, &blk)
141
- end
142
-
143
- def finish(replies, &blk)
144
- if blk
145
- futures.each_with_index.map do |future, i|
146
- future._set(blk.call(replies[i]))
147
- end
148
- else
149
- futures.each_with_index.map do |future, i|
150
- future._set(replies[i])
151
- end
152
- end
153
- end
154
-
155
- class Multi < self
156
- def finish(replies)
157
- exec = replies.last
158
-
159
- return if exec.nil? # The transaction failed because of WATCH.
160
-
161
- # EXEC command failed.
162
- raise exec if exec.is_a?(CommandError)
163
-
164
- if exec.size < futures.size
165
- # Some command wasn't recognized by Redis.
166
- command_error = replies.detect { |r| r.is_a?(CommandError) }
167
- raise command_error
168
- end
169
-
170
- super(exec) do |reply|
171
- # Because an EXEC returns nested replies, hiredis won't be able to
172
- # convert an error reply to a CommandError instance itself. This is
173
- # specific to MULTI/EXEC, so we solve this here.
174
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
175
- end
176
- end
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
-
198
- def commands
199
- if empty?
200
- []
201
- else
202
- [[:multi]] + super + [[:exec]]
203
- end
48
+ def send_blocking_command(command, timeout, &block)
49
+ future = Future.new(command, block)
50
+ @pipeline.blocking_call_v(timeout, command) do |result|
51
+ future._set(result)
204
52
  end
53
+ @futures << future
54
+ future
205
55
  end
206
56
  end
207
57
 
208
- class DeprecatedPipeline < DelegateClass(Pipeline)
209
- def initialize(pipeline)
210
- super(pipeline)
211
- @deprecation_displayed = false
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
212
61
  end
213
62
 
214
- def __getobj__
215
- unless @deprecation_displayed
216
- Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 10))
217
- @deprecation_displayed = true
218
- end
219
- @delegate_dc_obj
220
- end
221
- end
63
+ private
222
64
 
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
65
+ # Blocking commands inside transaction behave like non-blocking.
66
+ # It shouldn't be done though.
67
+ # https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
68
+ def send_blocking_command(command, _timeout, &block)
69
+ send_command(command, &block)
235
70
  end
236
71
  end
237
72
 
@@ -244,23 +79,10 @@ class Redis
244
79
  class Future < BasicObject
245
80
  FutureNotReady = ::Redis::FutureNotReady.new
246
81
 
247
- attr_reader :timeout
248
-
249
- def initialize(command, transformation, timeout)
82
+ def initialize(command, coerce)
250
83
  @command = command
251
- @transformation = transformation
252
- @timeout = timeout
253
84
  @object = FutureNotReady
254
- end
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
85
+ @coerce = coerce
264
86
  end
265
87
 
266
88
  def inspect
@@ -268,16 +90,12 @@ class Redis
268
90
  end
269
91
 
270
92
  def _set(object)
271
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
272
94
  value
273
95
  end
274
96
 
275
- def _command
276
- @command
277
- end
278
-
279
97
  def value
280
- ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
281
99
  @object
282
100
  end
283
101
 
@@ -294,13 +112,16 @@ class Redis
294
112
  def initialize(futures)
295
113
  @futures = futures
296
114
  @command = [:exec]
115
+ @object = FutureNotReady
297
116
  end
298
117
 
299
118
  def _set(replies)
300
- @futures.each_with_index do |future, index|
301
- future._set(replies[index])
119
+ if replies
120
+ @futures.each_with_index do |future, index|
121
+ future._set(replies[index])
122
+ end
302
123
  end
303
- replies
124
+ @object = replies
304
125
  end
305
126
  end
306
127
  end
@@ -4,10 +4,13 @@ class Redis
4
4
  class SubscribedClient
5
5
  def initialize(client)
6
6
  @client = client
7
+ @write_monitor = Monitor.new
7
8
  end
8
9
 
9
- def call(command)
10
- @client.process([command])
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
11
14
  end
12
15
 
13
16
  def subscribe(*channels, &block)
@@ -27,11 +30,15 @@ class Redis
27
30
  end
28
31
 
29
32
  def unsubscribe(*channels)
30
- call([:unsubscribe, *channels])
33
+ call_v([:unsubscribe, *channels])
31
34
  end
32
35
 
33
36
  def punsubscribe(*channels)
34
- call([:punsubscribe, *channels])
37
+ call_v([:punsubscribe, *channels])
38
+ end
39
+
40
+ def close
41
+ @client.close
35
42
  end
36
43
 
37
44
  protected
@@ -39,13 +46,17 @@ class Redis
39
46
  def subscription(start, stop, channels, block, timeout = 0)
40
47
  sub = Subscription.new(&block)
41
48
 
42
- unsubscribed = false
43
-
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
49
+ call_v([start, *channels])
50
+ while event = @client.next_event(timeout)
51
+ if event.is_a?(::RedisClient::CommandError)
52
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
53
+ end
54
+
55
+ type, *rest = event
56
+ if callback = sub.callbacks[type]
57
+ callback.call(*rest)
58
+ end
59
+ break if type == stop && rest.last == 0
49
60
  end
50
61
  # No need to unsubscribe here. The real client closes the connection
51
62
  # whenever an exception is raised (see #ensure_connected).
@@ -56,10 +67,7 @@ class Redis
56
67
  attr :callbacks
57
68
 
58
69
  def initialize
59
- @callbacks = Hash.new do |hash, key|
60
- hash[key] = ->(*_) {}
61
- end
62
-
70
+ @callbacks = {}
63
71
  yield(self)
64
72
  end
65
73
 
data/lib/redis/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Redis
4
- VERSION = '4.7.1'
4
+ VERSION = '5.0.4'
5
5
  end