redis 3.3.5 → 5.0.7
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 +5 -5
- data/CHANGELOG.md +290 -2
- data/README.md +146 -146
- data/lib/redis/client.rb +79 -541
- data/lib/redis/commands/bitmaps.rb +66 -0
- data/lib/redis/commands/cluster.rb +28 -0
- data/lib/redis/commands/connection.rb +53 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +254 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +437 -0
- data/lib/redis/commands/lists.rb +339 -0
- data/lib/redis/commands/pubsub.rb +54 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +214 -0
- data/lib/redis/commands/sorted_sets.rb +884 -0
- data/lib/redis/commands/streams.rb +402 -0
- data/lib/redis/commands/strings.rb +314 -0
- data/lib/redis/commands/transactions.rb +115 -0
- data/lib/redis/commands.rb +237 -0
- data/lib/redis/distributed.rb +328 -108
- data/lib/redis/errors.rb +23 -1
- data/lib/redis/hash_ring.rb +36 -79
- data/lib/redis/pipeline.rb +69 -83
- data/lib/redis/subscribe.rb +26 -19
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +115 -2695
- metadata +38 -218
- data/.gitignore +0 -16
- data/.travis/Gemfile +0 -11
- data/.travis.yml +0 -89
- data/.yardopts +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -87
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sentinel.rb +0 -41
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/lib/redis/connection/command_helper.rb +0 -44
- data/lib/redis/connection/hiredis.rb +0 -66
- data/lib/redis/connection/registry.rb +0 -12
- data/lib/redis/connection/ruby.rb +0 -429
- data/lib/redis/connection/synchrony.rb +0 -133
- data/lib/redis/connection.rb +0 -9
- data/redis.gemspec +0 -44
- data/test/bitpos_test.rb +0 -69
- data/test/blocking_commands_test.rb +0 -42
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_hyper_log_log_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -137
- data/test/commands_on_strings_test.rb +0 -101
- data/test/commands_on_value_types_test.rb +0 -133
- data/test/connection_handling_test.rb +0 -277
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -59
- data/test/distributed_commands_on_value_types_test.rb +0 -95
- data/test/distributed_commands_requiring_clustering_test.rb +0 -164
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -79
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -66
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/fork_safety_test.rb +0 -65
- data/test/helper.rb +0 -232
- data/test/helper_test.rb +0 -24
- data/test/internals_test.rb +0 -417
- data/test/lint/blocking_commands.rb +0 -150
- data/test/lint/hashes.rb +0 -162
- data/test/lint/hyper_log_log.rb +0 -60
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -140
- data/test/lint/sorted_sets.rb +0 -316
- data/test/lint/strings.rb +0 -260
- data/test/lint/value_types.rb +0 -122
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -242
- data/test/publish_subscribe_test.rb +0 -282
- data/test/remote_server_control_commands_test.rb +0 -118
- data/test/scanning_test.rb +0 -413
- data/test/scripting_test.rb +0 -78
- data/test/sentinel_command_test.rb +0 -80
- data/test/sentinel_test.rb +0 -255
- data/test/sorting_test.rb +0 -59
- data/test/ssl_test.rb +0 -73
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -88
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -62
- data/test/transactions_test.rb +0 -264
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -138
data/lib/redis/client.rb
CHANGED
@@ -1,590 +1,128 @@
|
|
1
|
-
|
2
|
-
require "socket"
|
3
|
-
require "cgi"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
class Client
|
3
|
+
require 'redis-client'
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
5
|
+
class Redis
|
6
|
+
class Client < ::RedisClient
|
7
|
+
ERROR_MAPPING = {
|
8
|
+
RedisClient::ConnectionError => Redis::ConnectionError,
|
9
|
+
RedisClient::CommandError => Redis::CommandError,
|
10
|
+
RedisClient::ReadTimeoutError => Redis::TimeoutError,
|
11
|
+
RedisClient::CannotConnectError => Redis::CannotConnectError,
|
12
|
+
RedisClient::AuthenticationError => Redis::CannotConnectError,
|
13
|
+
RedisClient::FailoverError => Redis::CannotConnectError,
|
14
|
+
RedisClient::PermissionError => Redis::PermissionError,
|
15
|
+
RedisClient::WrongTypeError => Redis::WrongTypeError,
|
16
|
+
RedisClient::ReadOnlyError => Redis::ReadOnlyError,
|
17
|
+
RedisClient::ProtocolError => Redis::ProtocolError,
|
18
|
+
RedisClient::OutOfMemoryError => Redis::OutOfMemoryError,
|
22
19
|
}
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
def scheme
|
29
|
-
@options[:scheme]
|
30
|
-
end
|
31
|
-
|
32
|
-
def host
|
33
|
-
@options[:host]
|
34
|
-
end
|
35
|
-
|
36
|
-
def port
|
37
|
-
@options[:port]
|
38
|
-
end
|
39
|
-
|
40
|
-
def path
|
41
|
-
@options[:path]
|
42
|
-
end
|
43
|
-
|
44
|
-
def read_timeout
|
45
|
-
@options[:read_timeout]
|
46
|
-
end
|
47
|
-
|
48
|
-
def connect_timeout
|
49
|
-
@options[:connect_timeout]
|
50
|
-
end
|
51
|
-
|
52
|
-
def timeout
|
53
|
-
@options[:read_timeout]
|
54
|
-
end
|
55
|
-
|
56
|
-
def password
|
57
|
-
@options[:password]
|
58
|
-
end
|
59
|
-
|
60
|
-
def db
|
61
|
-
@options[:db]
|
62
|
-
end
|
63
|
-
|
64
|
-
def db=(db)
|
65
|
-
@options[:db] = db.to_i
|
66
|
-
end
|
67
|
-
|
68
|
-
def driver
|
69
|
-
@options[:driver]
|
70
|
-
end
|
71
|
-
|
72
|
-
def inherit_socket?
|
73
|
-
@options[:inherit_socket]
|
74
|
-
end
|
75
|
-
|
76
|
-
attr_accessor :logger
|
77
|
-
attr_reader :connection
|
78
|
-
attr_reader :command_map
|
79
|
-
|
80
|
-
def initialize(options = {})
|
81
|
-
@options = _parse_options(options)
|
82
|
-
@reconnect = true
|
83
|
-
@logger = @options[:logger]
|
84
|
-
@connection = nil
|
85
|
-
@command_map = {}
|
86
|
-
|
87
|
-
@pending_reads = 0
|
88
|
-
|
89
|
-
if options.include?(:sentinels)
|
90
|
-
@connector = Connector::Sentinel.new(@options)
|
91
|
-
else
|
92
|
-
@connector = Connector.new(@options)
|
21
|
+
class << self
|
22
|
+
def config(**kwargs)
|
23
|
+
super(protocol: 2, **kwargs)
|
93
24
|
end
|
94
|
-
end
|
95
25
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
# Don't try to reconnect when the connection is fresh
|
100
|
-
with_reconnect(false) do
|
101
|
-
establish_connection
|
102
|
-
call [:auth, password] if password
|
103
|
-
call [:select, db] if db != 0
|
104
|
-
call [:client, :setname, @options[:id]] if @options[:id]
|
105
|
-
@connector.check(self)
|
26
|
+
def sentinel(**kwargs)
|
27
|
+
super(protocol: 2, **kwargs, client_implementation: ::RedisClient)
|
106
28
|
end
|
107
29
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
def id
|
112
|
-
@options[:id] || "redis://#{location}/#{db}"
|
113
|
-
end
|
114
|
-
|
115
|
-
def location
|
116
|
-
path || "#{host}:#{port}"
|
117
|
-
end
|
118
|
-
|
119
|
-
def call(command)
|
120
|
-
reply = process([command]) { read }
|
121
|
-
raise reply if reply.is_a?(CommandError)
|
122
|
-
|
123
|
-
if block_given?
|
124
|
-
yield reply
|
125
|
-
else
|
126
|
-
reply
|
30
|
+
def translate_error!(error)
|
31
|
+
redis_error = translate_error_class(error.class)
|
32
|
+
raise redis_error, error.message, error.backtrace
|
127
33
|
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def call_loop(command, timeout = 0)
|
131
|
-
error = nil
|
132
|
-
|
133
|
-
result = with_socket_timeout(timeout) do
|
134
|
-
process([command]) do
|
135
|
-
loop do
|
136
|
-
reply = read
|
137
|
-
if reply.is_a?(CommandError)
|
138
|
-
error = reply
|
139
|
-
break
|
140
|
-
else
|
141
|
-
yield reply
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# Raise error when previous block broke out of the loop.
|
148
|
-
raise error if error
|
149
|
-
|
150
|
-
# Result is set to the value that the provided block used to break.
|
151
|
-
result
|
152
|
-
end
|
153
|
-
|
154
|
-
def call_pipeline(pipeline)
|
155
|
-
with_reconnect pipeline.with_reconnect? do
|
156
|
-
begin
|
157
|
-
pipeline.finish(call_pipelined(pipeline.commands)).tap do
|
158
|
-
self.db = pipeline.db if pipeline.db
|
159
|
-
end
|
160
|
-
rescue ConnectionError => e
|
161
|
-
return nil if pipeline.shutdown?
|
162
|
-
# Assume the pipeline was sent in one piece, but execution of
|
163
|
-
# SHUTDOWN caused none of the replies for commands that were executed
|
164
|
-
# prior to it from coming back around.
|
165
|
-
raise e
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def call_pipelined(commands)
|
171
|
-
return [] if commands.empty?
|
172
|
-
|
173
|
-
# The method #ensure_connected (called from #process) reconnects once on
|
174
|
-
# I/O errors. To make an effort in making sure that commands are not
|
175
|
-
# executed more than once, only allow reconnection before the first reply
|
176
|
-
# has been read. When an error occurs after the first reply has been
|
177
|
-
# read, retrying would re-execute the entire pipeline, thus re-issuing
|
178
|
-
# already successfully executed commands. To circumvent this, don't retry
|
179
|
-
# after the first reply has been read successfully.
|
180
|
-
|
181
|
-
result = Array.new(commands.size)
|
182
|
-
reconnect = @reconnect
|
183
|
-
|
184
|
-
begin
|
185
|
-
exception = nil
|
186
|
-
|
187
|
-
process(commands) do
|
188
|
-
result[0] = read
|
189
34
|
|
190
|
-
|
35
|
+
private
|
191
36
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
37
|
+
def translate_error_class(error_class)
|
38
|
+
ERROR_MAPPING.fetch(error_class)
|
39
|
+
rescue IndexError
|
40
|
+
if (client_error = error_class.ancestors.find { |a| ERROR_MAPPING[a] })
|
41
|
+
ERROR_MAPPING[error_class] = ERROR_MAPPING[client_error]
|
42
|
+
else
|
43
|
+
raise
|
197
44
|
end
|
198
|
-
|
199
|
-
raise exception if exception
|
200
|
-
ensure
|
201
|
-
@reconnect = reconnect
|
202
45
|
end
|
203
|
-
|
204
|
-
result
|
205
46
|
end
|
206
47
|
|
207
|
-
def
|
208
|
-
|
209
|
-
call(command, &blk)
|
210
|
-
end
|
211
|
-
rescue ConnectionError
|
212
|
-
retry
|
48
|
+
def id
|
49
|
+
config.id
|
213
50
|
end
|
214
51
|
|
215
|
-
def
|
216
|
-
|
52
|
+
def server_url
|
53
|
+
config.server_url
|
217
54
|
end
|
218
55
|
|
219
|
-
def
|
220
|
-
|
221
|
-
ensure_connected do
|
222
|
-
commands.each do |command|
|
223
|
-
if command_map[command.first]
|
224
|
-
command = command.dup
|
225
|
-
command[0] = command_map[command.first]
|
226
|
-
end
|
227
|
-
|
228
|
-
write(command)
|
229
|
-
end
|
230
|
-
|
231
|
-
yield if block_given?
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def connected?
|
237
|
-
!! (connection && connection.connected?)
|
56
|
+
def timeout
|
57
|
+
config.read_timeout
|
238
58
|
end
|
239
59
|
|
240
|
-
def
|
241
|
-
|
60
|
+
def db
|
61
|
+
config.db
|
242
62
|
end
|
243
63
|
|
244
|
-
def
|
245
|
-
|
246
|
-
connect
|
64
|
+
def host
|
65
|
+
config.host unless config.path
|
247
66
|
end
|
248
67
|
|
249
|
-
def
|
250
|
-
|
251
|
-
rescue TimeoutError => e1
|
252
|
-
# Add a message to the exception without destroying the original stack
|
253
|
-
e2 = TimeoutError.new("Connection timed out")
|
254
|
-
e2.set_backtrace(e1.backtrace)
|
255
|
-
raise e2
|
256
|
-
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
|
257
|
-
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
68
|
+
def port
|
69
|
+
config.port unless config.path
|
258
70
|
end
|
259
71
|
|
260
|
-
def
|
261
|
-
|
262
|
-
value = connection.read
|
263
|
-
@pending_reads -= 1
|
264
|
-
value
|
265
|
-
end
|
72
|
+
def path
|
73
|
+
config.path
|
266
74
|
end
|
267
75
|
|
268
|
-
def
|
269
|
-
|
270
|
-
@pending_reads += 1
|
271
|
-
connection.write(command)
|
272
|
-
end
|
76
|
+
def username
|
77
|
+
config.username
|
273
78
|
end
|
274
79
|
|
275
|
-
def
|
276
|
-
|
277
|
-
|
278
|
-
begin
|
279
|
-
connection.timeout = timeout
|
280
|
-
yield
|
281
|
-
ensure
|
282
|
-
connection.timeout = self.timeout if connected?
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def without_socket_timeout(&blk)
|
287
|
-
with_socket_timeout(0, &blk)
|
80
|
+
def password
|
81
|
+
config.password
|
288
82
|
end
|
289
83
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
ensure
|
295
|
-
@reconnect = original
|
296
|
-
end
|
297
|
-
end
|
84
|
+
undef_method :call
|
85
|
+
undef_method :call_once
|
86
|
+
undef_method :call_once_v
|
87
|
+
undef_method :blocking_call
|
298
88
|
|
299
|
-
def
|
300
|
-
|
89
|
+
def call_v(command, &block)
|
90
|
+
super(command, &block)
|
91
|
+
rescue ::RedisClient::Error => error
|
92
|
+
Client.translate_error!(error)
|
301
93
|
end
|
302
94
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
commands.each do |name, *args|
|
310
|
-
logged_args = args.map do |a|
|
311
|
-
case
|
312
|
-
when a.respond_to?(:inspect) then a.inspect
|
313
|
-
when a.respond_to?(:to_s) then a.to_s
|
314
|
-
else
|
315
|
-
# handle poorly-behaved descendants of BasicObject
|
316
|
-
klass = a.instance_exec { (class << self; self end).superclass }
|
317
|
-
"\#<#{klass}:#{a.__id__}>"
|
318
|
-
end
|
319
|
-
end
|
320
|
-
@logger.debug("[Redis] command=#{name.to_s.upcase} args=#{logged_args.join(' ')}")
|
321
|
-
end
|
322
|
-
|
323
|
-
t1 = Time.now
|
324
|
-
yield
|
325
|
-
ensure
|
326
|
-
@logger.debug("[Redis] call_time=%0.2f ms" % ((Time.now - t1) * 1000)) if t1
|
95
|
+
def blocking_call_v(timeout, command, &block)
|
96
|
+
if timeout && timeout > 0
|
97
|
+
# Can't use the command timeout argument as the connection timeout
|
98
|
+
# otherwise it would be very racy. So we add the regular read_timeout on top
|
99
|
+
# to account for the network delay.
|
100
|
+
timeout += config.read_timeout
|
327
101
|
end
|
328
|
-
end
|
329
|
-
|
330
|
-
def establish_connection
|
331
|
-
server = @connector.resolve.dup
|
332
102
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
@connection = @options[:driver].connect(@options)
|
337
|
-
@pending_reads = 0
|
338
|
-
rescue TimeoutError,
|
339
|
-
Errno::ECONNREFUSED,
|
340
|
-
Errno::EHOSTDOWN,
|
341
|
-
Errno::EHOSTUNREACH,
|
342
|
-
Errno::ENETUNREACH,
|
343
|
-
Errno::ETIMEDOUT
|
344
|
-
|
345
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
|
103
|
+
super(timeout, command, &block)
|
104
|
+
rescue ::RedisClient::Error => error
|
105
|
+
Client.translate_error!(error)
|
346
106
|
end
|
347
107
|
|
348
|
-
def
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
begin
|
354
|
-
attempts += 1
|
355
|
-
|
356
|
-
if connected?
|
357
|
-
unless inherit_socket? || Process.pid == @pid
|
358
|
-
raise InheritedError,
|
359
|
-
"Tried to use a connection from a child process without reconnecting. " +
|
360
|
-
"You need to reconnect to Redis after forking " +
|
361
|
-
"or set :inherit_socket to true."
|
362
|
-
end
|
363
|
-
else
|
364
|
-
connect
|
365
|
-
end
|
366
|
-
|
367
|
-
yield
|
368
|
-
rescue BaseConnectionError
|
369
|
-
disconnect
|
370
|
-
|
371
|
-
if attempts <= @options[:reconnect_attempts] && @reconnect
|
372
|
-
retry
|
373
|
-
else
|
374
|
-
raise
|
375
|
-
end
|
376
|
-
rescue Exception
|
377
|
-
disconnect
|
378
|
-
raise
|
379
|
-
end
|
108
|
+
def pipelined
|
109
|
+
super
|
110
|
+
rescue ::RedisClient::Error => error
|
111
|
+
Client.translate_error!(error)
|
380
112
|
end
|
381
113
|
|
382
|
-
def
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
options = options.dup
|
387
|
-
|
388
|
-
defaults.keys.each do |key|
|
389
|
-
# Fill in defaults if needed
|
390
|
-
if defaults[key].respond_to?(:call)
|
391
|
-
defaults[key] = defaults[key].call
|
392
|
-
end
|
393
|
-
|
394
|
-
# Symbolize only keys that are needed
|
395
|
-
options[key] = options[key.to_s] if options.has_key?(key.to_s)
|
396
|
-
end
|
397
|
-
|
398
|
-
url = options[:url] || defaults[:url]
|
399
|
-
|
400
|
-
# Override defaults from URL if given
|
401
|
-
if url
|
402
|
-
require "uri"
|
403
|
-
|
404
|
-
uri = URI(url)
|
405
|
-
|
406
|
-
if uri.scheme == "unix"
|
407
|
-
defaults[:path] = uri.path
|
408
|
-
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
409
|
-
defaults[:scheme] = uri.scheme
|
410
|
-
defaults[:host] = uri.host if uri.host
|
411
|
-
defaults[:port] = uri.port if uri.port
|
412
|
-
defaults[:password] = CGI.unescape(uri.password) if uri.password
|
413
|
-
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
414
|
-
defaults[:role] = :master
|
415
|
-
else
|
416
|
-
raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
|
417
|
-
end
|
418
|
-
|
419
|
-
defaults[:ssl] = true if uri.scheme == "rediss"
|
420
|
-
end
|
421
|
-
|
422
|
-
# Use default when option is not specified or nil
|
423
|
-
defaults.keys.each do |key|
|
424
|
-
options[key] = defaults[key] if options[key].nil?
|
425
|
-
end
|
426
|
-
|
427
|
-
if options[:path]
|
428
|
-
# Unix socket
|
429
|
-
options[:scheme] = "unix"
|
430
|
-
options.delete(:host)
|
431
|
-
options.delete(:port)
|
432
|
-
else
|
433
|
-
# TCP socket
|
434
|
-
options[:host] = options[:host].to_s
|
435
|
-
options[:port] = options[:port].to_i
|
436
|
-
end
|
437
|
-
|
438
|
-
if options.has_key?(:timeout)
|
439
|
-
options[:connect_timeout] ||= options[:timeout]
|
440
|
-
options[:read_timeout] ||= options[:timeout]
|
441
|
-
options[:write_timeout] ||= options[:timeout]
|
442
|
-
end
|
443
|
-
|
444
|
-
options[:connect_timeout] = Float(options[:connect_timeout])
|
445
|
-
options[:read_timeout] = Float(options[:read_timeout])
|
446
|
-
options[:write_timeout] = Float(options[:write_timeout])
|
447
|
-
|
448
|
-
options[:db] = options[:db].to_i
|
449
|
-
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
450
|
-
|
451
|
-
case options[:tcp_keepalive]
|
452
|
-
when Hash
|
453
|
-
[:time, :intvl, :probes].each do |key|
|
454
|
-
unless options[:tcp_keepalive][key].is_a?(Integer)
|
455
|
-
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
when Integer
|
460
|
-
if options[:tcp_keepalive] >= 60
|
461
|
-
options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
|
462
|
-
|
463
|
-
elsif options[:tcp_keepalive] >= 30
|
464
|
-
options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
|
465
|
-
|
466
|
-
elsif options[:tcp_keepalive] >= 5
|
467
|
-
options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
options[:_parsed] = true
|
472
|
-
|
473
|
-
options
|
114
|
+
def multi
|
115
|
+
super
|
116
|
+
rescue ::RedisClient::Error => error
|
117
|
+
Client.translate_error!(error)
|
474
118
|
end
|
475
119
|
|
476
|
-
def
|
477
|
-
|
478
|
-
|
479
|
-
if driver.kind_of?(String)
|
480
|
-
begin
|
481
|
-
require "redis/connection/#{driver}"
|
482
|
-
driver = Connection.const_get(driver.capitalize)
|
483
|
-
rescue LoadError, NameError
|
484
|
-
raise RuntimeError, "Cannot load driver #{driver.inspect}"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
driver
|
120
|
+
def disable_reconnection(&block)
|
121
|
+
ensure_connected(retryable: false, &block)
|
489
122
|
end
|
490
123
|
|
491
|
-
|
492
|
-
|
493
|
-
@options = options.dup
|
494
|
-
end
|
495
|
-
|
496
|
-
def resolve
|
497
|
-
@options
|
498
|
-
end
|
499
|
-
|
500
|
-
def check(client)
|
501
|
-
end
|
502
|
-
|
503
|
-
class Sentinel < Connector
|
504
|
-
def initialize(options)
|
505
|
-
super(options)
|
506
|
-
|
507
|
-
@options[:password] = DEFAULTS.fetch(:password)
|
508
|
-
@options[:db] = DEFAULTS.fetch(:db)
|
509
|
-
|
510
|
-
@sentinels = @options.delete(:sentinels).dup
|
511
|
-
@role = @options.fetch(:role, "master").to_s
|
512
|
-
@master = @options[:host]
|
513
|
-
end
|
514
|
-
|
515
|
-
def check(client)
|
516
|
-
# Check the instance is really of the role we are looking for.
|
517
|
-
# We can't assume the command is supported since it was introduced
|
518
|
-
# recently and this client should work with old stuff.
|
519
|
-
begin
|
520
|
-
role = client.call([:role])[0]
|
521
|
-
rescue Redis::CommandError
|
522
|
-
# Assume the test is passed if we can't get a reply from ROLE...
|
523
|
-
role = @role
|
524
|
-
end
|
525
|
-
|
526
|
-
if role != @role
|
527
|
-
client.disconnect
|
528
|
-
raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
def resolve
|
533
|
-
result = case @role
|
534
|
-
when "master"
|
535
|
-
resolve_master
|
536
|
-
when "slave"
|
537
|
-
resolve_slave
|
538
|
-
else
|
539
|
-
raise ArgumentError, "Unknown instance role #{@role}"
|
540
|
-
end
|
541
|
-
|
542
|
-
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
543
|
-
end
|
544
|
-
|
545
|
-
def sentinel_detect
|
546
|
-
@sentinels.each do |sentinel|
|
547
|
-
client = Client.new(@options.merge({
|
548
|
-
:host => sentinel[:host],
|
549
|
-
:port => sentinel[:port],
|
550
|
-
:reconnect_attempts => 0,
|
551
|
-
}))
|
552
|
-
|
553
|
-
begin
|
554
|
-
if result = yield(client)
|
555
|
-
# This sentinel responded. Make sure we ask it first next time.
|
556
|
-
@sentinels.delete(sentinel)
|
557
|
-
@sentinels.unshift(sentinel)
|
558
|
-
|
559
|
-
return result
|
560
|
-
end
|
561
|
-
rescue BaseConnectionError
|
562
|
-
ensure
|
563
|
-
client.disconnect
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
raise CannotConnectError, "No sentinels available."
|
568
|
-
end
|
569
|
-
|
570
|
-
def resolve_master
|
571
|
-
sentinel_detect do |client|
|
572
|
-
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
573
|
-
{:host => reply[0], :port => reply[1]}
|
574
|
-
end
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
def resolve_slave
|
579
|
-
sentinel_detect do |client|
|
580
|
-
if reply = client.call(["sentinel", "slaves", @master])
|
581
|
-
slave = Hash[*reply.sample]
|
582
|
-
|
583
|
-
{:host => slave.fetch("ip"), :port => slave.fetch("port")}
|
584
|
-
end
|
585
|
-
end
|
586
|
-
end
|
587
|
-
end
|
124
|
+
def inherit_socket!
|
125
|
+
@inherit_socket = true
|
588
126
|
end
|
589
127
|
end
|
590
128
|
end
|