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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +75 -161
- data/lib/redis/client.rb +92 -608
- data/lib/redis/commands/bitmaps.rb +4 -1
- data/lib/redis/commands/cluster.rb +1 -18
- data/lib/redis/commands/connection.rb +5 -10
- data/lib/redis/commands/geo.rb +3 -3
- data/lib/redis/commands/hashes.rb +8 -5
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +53 -27
- data/lib/redis/commands/lists.rb +19 -23
- data/lib/redis/commands/pubsub.rb +7 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +43 -36
- data/lib/redis/commands/sorted_sets.rb +18 -12
- data/lib/redis/commands/streams.rb +12 -10
- data/lib/redis/commands/strings.rb +16 -15
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +1 -8
- data/lib/redis/distributed.rb +100 -67
- data/lib/redis/errors.rb +14 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +43 -222
- data/lib/redis/subscribe.rb +23 -15
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +88 -182
- metadata +9 -53
- data/lib/redis/cluster/command.rb +0 -79
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -120
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -34
- data/lib/redis/cluster/option.rb +0 -100
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -46
- data/lib/redis/cluster.rb +0 -315
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -66
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -437
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
data/lib/redis/pipeline.rb
CHANGED
@@ -4,27 +4,30 @@ require "delegate"
|
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class PipelinedConnection
|
7
|
-
|
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
|
26
|
-
|
27
|
-
|
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
|
-
|
38
|
-
|
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
|
116
|
-
|
117
|
-
|
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
|
209
|
-
def
|
210
|
-
|
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
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
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
|
-
|
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 = @
|
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?(::
|
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
|
-
|
301
|
-
future
|
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
|
data/lib/redis/subscribe.rb
CHANGED
@@ -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
|
10
|
-
@
|
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
|
-
|
33
|
+
call_v([:unsubscribe, *channels])
|
31
34
|
end
|
32
35
|
|
33
36
|
def punsubscribe(*channels)
|
34
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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 =
|
60
|
-
hash[key] = ->(*_) {}
|
61
|
-
end
|
62
|
-
|
70
|
+
@callbacks = {}
|
63
71
|
yield(self)
|
64
72
|
end
|
65
73
|
|
data/lib/redis/version.rb
CHANGED