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.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +290 -2
  3. data/README.md +146 -146
  4. data/lib/redis/client.rb +79 -541
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +328 -108
  23. data/lib/redis/errors.rb +23 -1
  24. data/lib/redis/hash_ring.rb +36 -79
  25. data/lib/redis/pipeline.rb +69 -83
  26. data/lib/redis/subscribe.rb +26 -19
  27. data/lib/redis/version.rb +3 -1
  28. data/lib/redis.rb +115 -2695
  29. metadata +38 -218
  30. data/.gitignore +0 -16
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -89
  33. data/.yardopts +0 -3
  34. data/Gemfile +0 -4
  35. data/Rakefile +0 -87
  36. data/benchmarking/logging.rb +0 -71
  37. data/benchmarking/pipeline.rb +0 -51
  38. data/benchmarking/speed.rb +0 -21
  39. data/benchmarking/suite.rb +0 -24
  40. data/benchmarking/worker.rb +0 -71
  41. data/examples/basic.rb +0 -15
  42. data/examples/consistency.rb +0 -114
  43. data/examples/dist_redis.rb +0 -43
  44. data/examples/incr-decr.rb +0 -17
  45. data/examples/list.rb +0 -26
  46. data/examples/pubsub.rb +0 -37
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sentinel.rb +0 -41
  50. data/examples/sets.rb +0 -36
  51. data/examples/unicorn/config.ru +0 -3
  52. data/examples/unicorn/unicorn.rb +0 -20
  53. data/lib/redis/connection/command_helper.rb +0 -44
  54. data/lib/redis/connection/hiredis.rb +0 -66
  55. data/lib/redis/connection/registry.rb +0 -12
  56. data/lib/redis/connection/ruby.rb +0 -429
  57. data/lib/redis/connection/synchrony.rb +0 -133
  58. data/lib/redis/connection.rb +0 -9
  59. data/redis.gemspec +0 -44
  60. data/test/bitpos_test.rb +0 -69
  61. data/test/blocking_commands_test.rb +0 -42
  62. data/test/client_test.rb +0 -59
  63. data/test/command_map_test.rb +0 -30
  64. data/test/commands_on_hashes_test.rb +0 -21
  65. data/test/commands_on_hyper_log_log_test.rb +0 -21
  66. data/test/commands_on_lists_test.rb +0 -20
  67. data/test/commands_on_sets_test.rb +0 -77
  68. data/test/commands_on_sorted_sets_test.rb +0 -137
  69. data/test/commands_on_strings_test.rb +0 -101
  70. data/test/commands_on_value_types_test.rb +0 -133
  71. data/test/connection_handling_test.rb +0 -277
  72. data/test/connection_test.rb +0 -57
  73. data/test/db/.gitkeep +0 -0
  74. data/test/distributed_blocking_commands_test.rb +0 -46
  75. data/test/distributed_commands_on_hashes_test.rb +0 -10
  76. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  77. data/test/distributed_commands_on_lists_test.rb +0 -22
  78. data/test/distributed_commands_on_sets_test.rb +0 -83
  79. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  80. data/test/distributed_commands_on_strings_test.rb +0 -59
  81. data/test/distributed_commands_on_value_types_test.rb +0 -95
  82. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  83. data/test/distributed_connection_handling_test.rb +0 -23
  84. data/test/distributed_internals_test.rb +0 -79
  85. data/test/distributed_key_tags_test.rb +0 -52
  86. data/test/distributed_persistence_control_commands_test.rb +0 -26
  87. data/test/distributed_publish_subscribe_test.rb +0 -92
  88. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  89. data/test/distributed_scripting_test.rb +0 -102
  90. data/test/distributed_sorting_test.rb +0 -20
  91. data/test/distributed_test.rb +0 -58
  92. data/test/distributed_transactions_test.rb +0 -32
  93. data/test/encoding_test.rb +0 -18
  94. data/test/error_replies_test.rb +0 -59
  95. data/test/fork_safety_test.rb +0 -65
  96. data/test/helper.rb +0 -232
  97. data/test/helper_test.rb +0 -24
  98. data/test/internals_test.rb +0 -417
  99. data/test/lint/blocking_commands.rb +0 -150
  100. data/test/lint/hashes.rb +0 -162
  101. data/test/lint/hyper_log_log.rb +0 -60
  102. data/test/lint/lists.rb +0 -143
  103. data/test/lint/sets.rb +0 -140
  104. data/test/lint/sorted_sets.rb +0 -316
  105. data/test/lint/strings.rb +0 -260
  106. data/test/lint/value_types.rb +0 -122
  107. data/test/persistence_control_commands_test.rb +0 -26
  108. data/test/pipelining_commands_test.rb +0 -242
  109. data/test/publish_subscribe_test.rb +0 -282
  110. data/test/remote_server_control_commands_test.rb +0 -118
  111. data/test/scanning_test.rb +0 -413
  112. data/test/scripting_test.rb +0 -78
  113. data/test/sentinel_command_test.rb +0 -80
  114. data/test/sentinel_test.rb +0 -255
  115. data/test/sorting_test.rb +0 -59
  116. data/test/ssl_test.rb +0 -73
  117. data/test/support/connection/hiredis.rb +0 -1
  118. data/test/support/connection/ruby.rb +0 -1
  119. data/test/support/connection/synchrony.rb +0 -17
  120. data/test/support/redis_mock.rb +0 -130
  121. data/test/support/ssl/gen_certs.sh +0 -31
  122. data/test/support/ssl/trusted-ca.crt +0 -25
  123. data/test/support/ssl/trusted-ca.key +0 -27
  124. data/test/support/ssl/trusted-cert.crt +0 -81
  125. data/test/support/ssl/trusted-cert.key +0 -28
  126. data/test/support/ssl/untrusted-ca.crt +0 -26
  127. data/test/support/ssl/untrusted-ca.key +0 -27
  128. data/test/support/ssl/untrusted-cert.crt +0 -82
  129. data/test/support/ssl/untrusted-cert.key +0 -28
  130. data/test/support/wire/synchrony.rb +0 -24
  131. data/test/support/wire/thread.rb +0 -5
  132. data/test/synchrony_driver.rb +0 -88
  133. data/test/test.conf.erb +0 -9
  134. data/test/thread_safety_test.rb +0 -62
  135. data/test/transactions_test.rb +0 -264
  136. data/test/unknown_commands_test.rb +0 -14
  137. data/test/url_param_test.rb +0 -138
data/lib/redis/client.rb CHANGED
@@ -1,590 +1,128 @@
1
- require "redis/errors"
2
- require "socket"
3
- require "cgi"
1
+ # frozen_string_literal: true
4
2
 
5
- class Redis
6
- class Client
3
+ require 'redis-client'
7
4
 
8
- DEFAULTS = {
9
- :url => lambda { ENV["REDIS_URL"] },
10
- :scheme => "redis",
11
- :host => "127.0.0.1",
12
- :port => 6379,
13
- :path => nil,
14
- :timeout => 5.0,
15
- :password => nil,
16
- :db => 0,
17
- :driver => nil,
18
- :id => nil,
19
- :tcp_keepalive => 0,
20
- :reconnect_attempts => 1,
21
- :inherit_socket => false
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
- def options
25
- Marshal.load(Marshal.dump(@options))
26
- end
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
- def connect
97
- @pid = Process.pid
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
- self
109
- end
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
- @reconnect = false
35
+ private
191
36
 
192
- (commands.size - 1).times do |i|
193
- reply = read
194
- result[i + 1] = reply
195
- exception = reply if exception.nil? && reply.is_a?(CommandError)
196
- end
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 call_with_timeout(command, timeout, &blk)
208
- with_socket_timeout(timeout) do
209
- call(command, &blk)
210
- end
211
- rescue ConnectionError
212
- retry
48
+ def id
49
+ config.id
213
50
  end
214
51
 
215
- def call_without_timeout(command, &blk)
216
- call_with_timeout(command, 0, &blk)
52
+ def server_url
53
+ config.server_url
217
54
  end
218
55
 
219
- def process(commands)
220
- logging(commands) do
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 disconnect
241
- connection.disconnect if connected?
60
+ def db
61
+ config.db
242
62
  end
243
63
 
244
- def reconnect
245
- disconnect
246
- connect
64
+ def host
65
+ config.host unless config.path
247
66
  end
248
67
 
249
- def io
250
- yield
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 read
261
- io do
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 write(command)
269
- io do
270
- @pending_reads += 1
271
- connection.write(command)
272
- end
76
+ def username
77
+ config.username
273
78
  end
274
79
 
275
- def with_socket_timeout(timeout)
276
- connect unless connected?
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
- def with_reconnect(val=true)
291
- begin
292
- original, @reconnect = @reconnect, val
293
- yield
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 without_reconnect(&blk)
300
- with_reconnect(false, &blk)
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
- protected
304
-
305
- def logging(commands)
306
- return yield unless @logger && @logger.debug?
307
-
308
- begin
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
- @options[:host] = server[:host]
334
- @options[:port] = Integer(server[:port]) if server.include?(:port)
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 ensure_connected
349
- disconnect if @pending_reads > 0
350
-
351
- attempts = 0
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 _parse_options(options)
383
- return options if options[:_parsed]
384
-
385
- defaults = DEFAULTS.dup
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 _parse_driver(driver)
477
- driver = driver.to_s if driver.is_a?(Symbol)
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
- class Connector
492
- def initialize(options)
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