redis 3.3.5 → 5.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|