redis 4.8.0 → 5.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -1
- data/README.md +101 -161
- data/lib/redis/client.rb +82 -625
- 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 +9 -6
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +5 -23
- data/lib/redis/commands/lists.rb +74 -25
- data/lib/redis/commands/pubsub.rb +28 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +31 -40
- data/lib/redis/commands/sorted_sets.rb +84 -12
- data/lib/redis/commands/streams.rb +39 -19
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +4 -7
- data/lib/redis/distributed.rb +114 -64
- data/lib/redis/errors.rb +15 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +43 -222
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +76 -184
- metadata +10 -54
- 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 -68
- 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)
|
@@ -26,12 +29,28 @@ class Redis
|
|
26
29
|
subscription("psubscribe", "punsubscribe", channels, block, timeout)
|
27
30
|
end
|
28
31
|
|
32
|
+
def ssubscribe(*channels, &block)
|
33
|
+
subscription("ssubscribe", "sunsubscribe", channels, block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ssubscribe_with_timeout(timeout, *channels, &block)
|
37
|
+
subscription("ssubscribe", "sunsubscribe", channels, block, timeout)
|
38
|
+
end
|
39
|
+
|
29
40
|
def unsubscribe(*channels)
|
30
|
-
|
41
|
+
call_v([:unsubscribe, *channels])
|
31
42
|
end
|
32
43
|
|
33
44
|
def punsubscribe(*channels)
|
34
|
-
|
45
|
+
call_v([:punsubscribe, *channels])
|
46
|
+
end
|
47
|
+
|
48
|
+
def sunsubscribe(*channels)
|
49
|
+
call_v([:sunsubscribe, *channels])
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@client.close
|
35
54
|
end
|
36
55
|
|
37
56
|
protected
|
@@ -39,13 +58,21 @@ class Redis
|
|
39
58
|
def subscription(start, stop, channels, block, timeout = 0)
|
40
59
|
sub = Subscription.new(&block)
|
41
60
|
|
42
|
-
|
61
|
+
case start
|
62
|
+
when "ssubscribe" then channels.each { |c| call_v([start, c]) } # avoid cross-slot keys
|
63
|
+
else call_v([start, *channels])
|
64
|
+
end
|
43
65
|
|
44
|
-
@client.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
66
|
+
while event = @client.next_event(timeout)
|
67
|
+
if event.is_a?(::RedisClient::CommandError)
|
68
|
+
raise Client::ERROR_MAPPING.fetch(event.class), event.message
|
69
|
+
end
|
70
|
+
|
71
|
+
type, *rest = event
|
72
|
+
if callback = sub.callbacks[type]
|
73
|
+
callback.call(*rest)
|
74
|
+
end
|
75
|
+
break if type == stop && rest.last == 0
|
49
76
|
end
|
50
77
|
# No need to unsubscribe here. The real client closes the connection
|
51
78
|
# whenever an exception is raised (see #ensure_connected).
|
@@ -56,10 +83,7 @@ class Redis
|
|
56
83
|
attr :callbacks
|
57
84
|
|
58
85
|
def initialize
|
59
|
-
@callbacks =
|
60
|
-
hash[key] = ->(*_) {}
|
61
|
-
end
|
62
|
-
|
86
|
+
@callbacks = {}
|
63
87
|
yield(self)
|
64
88
|
end
|
65
89
|
|
@@ -86,5 +110,17 @@ class Redis
|
|
86
110
|
def pmessage(&block)
|
87
111
|
@callbacks["pmessage"] = block
|
88
112
|
end
|
113
|
+
|
114
|
+
def ssubscribe(&block)
|
115
|
+
@callbacks["ssubscribe"] = block
|
116
|
+
end
|
117
|
+
|
118
|
+
def sunsubscribe(&block)
|
119
|
+
@callbacks["sunsubscribe"] = block
|
120
|
+
end
|
121
|
+
|
122
|
+
def smessage(&block)
|
123
|
+
@callbacks["smessage"] = block
|
124
|
+
end
|
89
125
|
end
|
90
126
|
end
|
data/lib/redis/version.rb
CHANGED