redis 3.3.5 → 4.3.1
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 +132 -2
- data/README.md +144 -79
- data/lib/redis.rb +1174 -405
- data/lib/redis/client.rb +150 -90
- data/lib/redis/cluster.rb +295 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +34 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +107 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +93 -0
- data/lib/redis/cluster/slot.rb +86 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/connection.rb +4 -2
- data/lib/redis/connection/command_helper.rb +5 -10
- data/lib/redis/connection/hiredis.rb +6 -5
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +126 -128
- data/lib/redis/connection/synchrony.rb +21 -8
- data/lib/redis/distributed.rb +147 -72
- data/lib/redis/errors.rb +48 -0
- data/lib/redis/hash_ring.rb +30 -73
- data/lib/redis/pipeline.rb +55 -15
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +49 -202
- data/.gitignore +0 -16
- data/.travis.yml +0 -89
- data/.travis/Gemfile +0 -11
- 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.rb +0 -41
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- 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,29 +1,38 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "errors"
         | 
| 2 4 | 
             
            require "socket"
         | 
| 3 5 | 
             
            require "cgi"
         | 
| 4 6 |  | 
| 5 7 | 
             
            class Redis
         | 
| 6 8 | 
             
              class Client
         | 
| 7 | 
            -
             | 
| 9 | 
            +
                # Defaults are also used for converting string keys to symbols.
         | 
| 8 10 | 
             
                DEFAULTS = {
         | 
| 9 | 
            -
                  : | 
| 10 | 
            -
                  : | 
| 11 | 
            -
                  : | 
| 12 | 
            -
                  : | 
| 13 | 
            -
                  : | 
| 14 | 
            -
                  : | 
| 15 | 
            -
                  : | 
| 16 | 
            -
                  : | 
| 17 | 
            -
                  : | 
| 18 | 
            -
                  : | 
| 19 | 
            -
                  : | 
| 20 | 
            -
                  : | 
| 21 | 
            -
                  : | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 26 | 
            -
             | 
| 11 | 
            +
                  url: -> { ENV["REDIS_URL"] },
         | 
| 12 | 
            +
                  scheme: "redis",
         | 
| 13 | 
            +
                  host: "127.0.0.1",
         | 
| 14 | 
            +
                  port: 6379,
         | 
| 15 | 
            +
                  path: nil,
         | 
| 16 | 
            +
                  read_timeout: nil,
         | 
| 17 | 
            +
                  write_timeout: nil,
         | 
| 18 | 
            +
                  connect_timeout: nil,
         | 
| 19 | 
            +
                  timeout: 5.0,
         | 
| 20 | 
            +
                  username: nil,
         | 
| 21 | 
            +
                  password: nil,
         | 
| 22 | 
            +
                  db: 0,
         | 
| 23 | 
            +
                  driver: nil,
         | 
| 24 | 
            +
                  id: nil,
         | 
| 25 | 
            +
                  tcp_keepalive: 0,
         | 
| 26 | 
            +
                  reconnect_attempts: 1,
         | 
| 27 | 
            +
                  reconnect_delay: 0,
         | 
| 28 | 
            +
                  reconnect_delay_max: 0.5,
         | 
| 29 | 
            +
                  inherit_socket: false,
         | 
| 30 | 
            +
                  logger: nil,
         | 
| 31 | 
            +
                  sentinels: nil,
         | 
| 32 | 
            +
                  role: nil
         | 
| 33 | 
            +
                }.freeze
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                attr_reader :options
         | 
| 27 36 |  | 
| 28 37 | 
             
                def scheme
         | 
| 29 38 | 
             
                  @options[:scheme]
         | 
| @@ -53,6 +62,10 @@ class Redis | |
| 53 62 | 
             
                  @options[:read_timeout]
         | 
| 54 63 | 
             
                end
         | 
| 55 64 |  | 
| 65 | 
            +
                def username
         | 
| 66 | 
            +
                  @options[:username]
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 56 69 | 
             
                def password
         | 
| 57 70 | 
             
                  @options[:password]
         | 
| 58 71 | 
             
                end
         | 
| @@ -86,11 +99,14 @@ class Redis | |
| 86 99 |  | 
| 87 100 | 
             
                  @pending_reads = 0
         | 
| 88 101 |  | 
| 89 | 
            -
                   | 
| 90 | 
            -
                     | 
| 91 | 
            -
             | 
| 92 | 
            -
                     | 
| 93 | 
            -
             | 
| 102 | 
            +
                  @connector =
         | 
| 103 | 
            +
                    if !@options[:sentinels].nil?
         | 
| 104 | 
            +
                      Connector::Sentinel.new(@options)
         | 
| 105 | 
            +
                    elsif options.include?(:connector) && options[:connector].respond_to?(:new)
         | 
| 106 | 
            +
                      options.delete(:connector).new(@options)
         | 
| 107 | 
            +
                    else
         | 
| 108 | 
            +
                      Connector.new(@options)
         | 
| 109 | 
            +
                    end
         | 
| 94 110 | 
             
                end
         | 
| 95 111 |  | 
| 96 112 | 
             
                def connect
         | 
| @@ -99,7 +115,17 @@ class Redis | |
| 99 115 | 
             
                  # Don't try to reconnect when the connection is fresh
         | 
| 100 116 | 
             
                  with_reconnect(false) do
         | 
| 101 117 | 
             
                    establish_connection
         | 
| 102 | 
            -
                     | 
| 118 | 
            +
                    if password
         | 
| 119 | 
            +
                      if username
         | 
| 120 | 
            +
                        begin
         | 
| 121 | 
            +
                          call [:auth, username, password]
         | 
| 122 | 
            +
                        rescue CommandError # Likely on Redis < 6
         | 
| 123 | 
            +
                          call [:auth, password]
         | 
| 124 | 
            +
                        end
         | 
| 125 | 
            +
                      else
         | 
| 126 | 
            +
                        call [:auth, password]
         | 
| 127 | 
            +
                      end
         | 
| 128 | 
            +
                    end
         | 
| 103 129 | 
             
                    call [:select, db] if db != 0
         | 
| 104 130 | 
             
                    call [:client, :setname, @options[:id]] if @options[:id]
         | 
| 105 131 | 
             
                    @connector.check(self)
         | 
| @@ -120,7 +146,7 @@ class Redis | |
| 120 146 | 
             
                  reply = process([command]) { read }
         | 
| 121 147 | 
             
                  raise reply if reply.is_a?(CommandError)
         | 
| 122 148 |  | 
| 123 | 
            -
                  if block_given?
         | 
| 149 | 
            +
                  if block_given? && reply != 'QUEUED'
         | 
| 124 150 | 
             
                    yield reply
         | 
| 125 151 | 
             
                  else
         | 
| 126 152 | 
             
                    reply
         | 
| @@ -152,13 +178,16 @@ class Redis | |
| 152 178 | 
             
                end
         | 
| 153 179 |  | 
| 154 180 | 
             
                def call_pipeline(pipeline)
         | 
| 181 | 
            +
                  return [] if pipeline.futures.empty?
         | 
| 182 | 
            +
             | 
| 155 183 | 
             
                  with_reconnect pipeline.with_reconnect? do
         | 
| 156 184 | 
             
                    begin
         | 
| 157 | 
            -
                      pipeline.finish(call_pipelined(pipeline | 
| 185 | 
            +
                      pipeline.finish(call_pipelined(pipeline)).tap do
         | 
| 158 186 | 
             
                        self.db = pipeline.db if pipeline.db
         | 
| 159 187 | 
             
                      end
         | 
| 160 188 | 
             
                    rescue ConnectionError => e
         | 
| 161 189 | 
             
                      return nil if pipeline.shutdown?
         | 
| 190 | 
            +
             | 
| 162 191 | 
             
                      # Assume the pipeline was sent in one piece, but execution of
         | 
| 163 192 | 
             
                      # SHUTDOWN caused none of the replies for commands that were executed
         | 
| 164 193 | 
             
                      # prior to it from coming back around.
         | 
| @@ -167,8 +196,8 @@ class Redis | |
| 167 196 | 
             
                  end
         | 
| 168 197 | 
             
                end
         | 
| 169 198 |  | 
| 170 | 
            -
                def call_pipelined( | 
| 171 | 
            -
                  return [] if  | 
| 199 | 
            +
                def call_pipelined(pipeline)
         | 
| 200 | 
            +
                  return [] if pipeline.futures.empty?
         | 
| 172 201 |  | 
| 173 202 | 
             
                  # The method #ensure_connected (called from #process) reconnects once on
         | 
| 174 203 | 
             
                  # I/O errors. To make an effort in making sure that commands are not
         | 
| @@ -178,6 +207,8 @@ class Redis | |
| 178 207 | 
             
                  # already successfully executed commands. To circumvent this, don't retry
         | 
| 179 208 | 
             
                  # after the first reply has been read successfully.
         | 
| 180 209 |  | 
| 210 | 
            +
                  commands = pipeline.commands
         | 
| 211 | 
            +
             | 
| 181 212 | 
             
                  result = Array.new(commands.size)
         | 
| 182 213 | 
             
                  reconnect = @reconnect
         | 
| 183 214 |  | 
| @@ -185,13 +216,14 @@ class Redis | |
| 185 216 | 
             
                    exception = nil
         | 
| 186 217 |  | 
| 187 218 | 
             
                    process(commands) do
         | 
| 188 | 
            -
                       | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
                         | 
| 194 | 
            -
                        result[i | 
| 219 | 
            +
                      pipeline.timeouts.each_with_index do |timeout, i|
         | 
| 220 | 
            +
                        reply = if timeout
         | 
| 221 | 
            +
                          with_socket_timeout(timeout) { read }
         | 
| 222 | 
            +
                        else
         | 
| 223 | 
            +
                          read
         | 
| 224 | 
            +
                        end
         | 
| 225 | 
            +
                        result[i] = reply
         | 
| 226 | 
            +
                        @reconnect = false
         | 
| 195 227 | 
             
                        exception = reply if exception.nil? && reply.is_a?(CommandError)
         | 
| 196 228 | 
             
                      end
         | 
| 197 229 | 
             
                    end
         | 
| @@ -234,12 +266,13 @@ class Redis | |
| 234 266 | 
             
                end
         | 
| 235 267 |  | 
| 236 268 | 
             
                def connected?
         | 
| 237 | 
            -
                  !! | 
| 269 | 
            +
                  !!(connection && connection.connected?)
         | 
| 238 270 | 
             
                end
         | 
| 239 271 |  | 
| 240 272 | 
             
                def disconnect
         | 
| 241 273 | 
             
                  connection.disconnect if connected?
         | 
| 242 274 | 
             
                end
         | 
| 275 | 
            +
                alias close disconnect
         | 
| 243 276 |  | 
| 244 277 | 
             
                def reconnect
         | 
| 245 278 | 
             
                  disconnect
         | 
| @@ -274,12 +307,15 @@ class Redis | |
| 274 307 |  | 
| 275 308 | 
             
                def with_socket_timeout(timeout)
         | 
| 276 309 | 
             
                  connect unless connected?
         | 
| 310 | 
            +
                  original = @options[:read_timeout]
         | 
| 277 311 |  | 
| 278 312 | 
             
                  begin
         | 
| 279 313 | 
             
                    connection.timeout = timeout
         | 
| 314 | 
            +
                    @options[:read_timeout] = timeout # for reconnection
         | 
| 280 315 | 
             
                    yield
         | 
| 281 316 | 
             
                  ensure
         | 
| 282 317 | 
             
                    connection.timeout = self.timeout if connected?
         | 
| 318 | 
            +
                    @options[:read_timeout] = original
         | 
| 283 319 | 
             
                  end
         | 
| 284 320 | 
             
                end
         | 
| 285 321 |  | 
| @@ -287,30 +323,27 @@ class Redis | |
| 287 323 | 
             
                  with_socket_timeout(0, &blk)
         | 
| 288 324 | 
             
                end
         | 
| 289 325 |  | 
| 290 | 
            -
                def with_reconnect(val=true)
         | 
| 291 | 
            -
                   | 
| 292 | 
            -
             | 
| 293 | 
            -
             | 
| 294 | 
            -
                   | 
| 295 | 
            -
                    @reconnect = original
         | 
| 296 | 
            -
                  end
         | 
| 326 | 
            +
                def with_reconnect(val = true)
         | 
| 327 | 
            +
                  original, @reconnect = @reconnect, val
         | 
| 328 | 
            +
                  yield
         | 
| 329 | 
            +
                ensure
         | 
| 330 | 
            +
                  @reconnect = original
         | 
| 297 331 | 
             
                end
         | 
| 298 332 |  | 
| 299 333 | 
             
                def without_reconnect(&blk)
         | 
| 300 334 | 
             
                  with_reconnect(false, &blk)
         | 
| 301 335 | 
             
                end
         | 
| 302 336 |  | 
| 303 | 
            -
             | 
| 337 | 
            +
                protected
         | 
| 304 338 |  | 
| 305 339 | 
             
                def logging(commands)
         | 
| 306 | 
            -
                  return yield unless @logger | 
| 340 | 
            +
                  return yield unless @logger&.debug?
         | 
| 307 341 |  | 
| 308 342 | 
             
                  begin
         | 
| 309 343 | 
             
                    commands.each do |name, *args|
         | 
| 310 344 | 
             
                      logged_args = args.map do |a|
         | 
| 311 | 
            -
                         | 
| 312 | 
            -
                         | 
| 313 | 
            -
                        when a.respond_to?(:to_s)    then a.to_s
         | 
| 345 | 
            +
                        if a.respond_to?(:inspect) then a.inspect
         | 
| 346 | 
            +
                        elsif a.respond_to?(:to_s) then a.to_s
         | 
| 314 347 | 
             
                        else
         | 
| 315 348 | 
             
                          # handle poorly-behaved descendants of BasicObject
         | 
| 316 349 | 
             
                          klass = a.instance_exec { (class << self; self end).superclass }
         | 
| @@ -336,13 +369,17 @@ class Redis | |
| 336 369 | 
             
                  @connection = @options[:driver].connect(@options)
         | 
| 337 370 | 
             
                  @pending_reads = 0
         | 
| 338 371 | 
             
                rescue TimeoutError,
         | 
| 372 | 
            +
                       SocketError,
         | 
| 373 | 
            +
                       Errno::EADDRNOTAVAIL,
         | 
| 339 374 | 
             
                       Errno::ECONNREFUSED,
         | 
| 340 375 | 
             
                       Errno::EHOSTDOWN,
         | 
| 341 376 | 
             
                       Errno::EHOSTUNREACH,
         | 
| 342 377 | 
             
                       Errno::ENETUNREACH,
         | 
| 343 | 
            -
                       Errno:: | 
| 378 | 
            +
                       Errno::ENOENT,
         | 
| 379 | 
            +
                       Errno::ETIMEDOUT,
         | 
| 380 | 
            +
                       Errno::EINVAL => error
         | 
| 344 381 |  | 
| 345 | 
            -
                  raise CannotConnectError, "Error connecting to Redis on #{location} (#{ | 
| 382 | 
            +
                  raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
         | 
| 346 383 | 
             
                end
         | 
| 347 384 |  | 
| 348 385 | 
             
                def ensure_connected
         | 
| @@ -356,9 +393,9 @@ class Redis | |
| 356 393 | 
             
                    if connected?
         | 
| 357 394 | 
             
                      unless inherit_socket? || Process.pid == @pid
         | 
| 358 395 | 
             
                        raise InheritedError,
         | 
| 359 | 
            -
             | 
| 360 | 
            -
             | 
| 361 | 
            -
             | 
| 396 | 
            +
                              "Tried to use a connection from a child process without reconnecting. " \
         | 
| 397 | 
            +
                              "You need to reconnect to Redis after forking " \
         | 
| 398 | 
            +
                              "or set :inherit_socket to true."
         | 
| 362 399 | 
             
                      end
         | 
| 363 400 | 
             
                    else
         | 
| 364 401 | 
             
                      connect
         | 
| @@ -369,6 +406,10 @@ class Redis | |
| 369 406 | 
             
                    disconnect
         | 
| 370 407 |  | 
| 371 408 | 
             
                    if attempts <= @options[:reconnect_attempts] && @reconnect
         | 
| 409 | 
            +
                      sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
         | 
| 410 | 
            +
                                 @options[:reconnect_delay_max]].min
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                      Kernel.sleep(sleep_t)
         | 
| 372 413 | 
             
                      retry
         | 
| 373 414 | 
             
                    else
         | 
| 374 415 | 
             
                      raise
         | 
| @@ -387,15 +428,14 @@ class Redis | |
| 387 428 |  | 
| 388 429 | 
             
                  defaults.keys.each do |key|
         | 
| 389 430 | 
             
                    # Fill in defaults if needed
         | 
| 390 | 
            -
                    if defaults[key].respond_to?(:call)
         | 
| 391 | 
            -
                      defaults[key] = defaults[key].call
         | 
| 392 | 
            -
                    end
         | 
| 431 | 
            +
                    defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
         | 
| 393 432 |  | 
| 394 433 | 
             
                    # Symbolize only keys that are needed
         | 
| 395 | 
            -
                    options[key] = options[key.to_s] if options. | 
| 434 | 
            +
                    options[key] = options[key.to_s] if options.key?(key.to_s)
         | 
| 396 435 | 
             
                  end
         | 
| 397 436 |  | 
| 398 | 
            -
                  url = options[:url] | 
| 437 | 
            +
                  url = options[:url]
         | 
| 438 | 
            +
                  url = defaults[:url] if url.nil?
         | 
| 399 439 |  | 
| 400 440 | 
             
                  # Override defaults from URL if given
         | 
| 401 441 | 
             
                  if url
         | 
| @@ -404,12 +444,13 @@ class Redis | |
| 404 444 | 
             
                    uri = URI(url)
         | 
| 405 445 |  | 
| 406 446 | 
             
                    if uri.scheme == "unix"
         | 
| 407 | 
            -
                      defaults[:path] | 
| 447 | 
            +
                      defaults[:path] = uri.path
         | 
| 408 448 | 
             
                    elsif uri.scheme == "redis" || uri.scheme == "rediss"
         | 
| 409 449 | 
             
                      defaults[:scheme]   = uri.scheme
         | 
| 410 450 | 
             
                      defaults[:host]     = uri.host if uri.host
         | 
| 411 451 | 
             
                      defaults[:port]     = uri.port if uri.port
         | 
| 412 | 
            -
                      defaults[: | 
| 452 | 
            +
                      defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
         | 
| 453 | 
            +
                      defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
         | 
| 413 454 | 
             
                      defaults[:db]       = uri.path[1..-1].to_i if uri.path
         | 
| 414 455 | 
             
                      defaults[:role] = :master
         | 
| 415 456 | 
             
                    else
         | 
| @@ -435,7 +476,7 @@ class Redis | |
| 435 476 | 
             
                    options[:port] = options[:port].to_i
         | 
| 436 477 | 
             
                  end
         | 
| 437 478 |  | 
| 438 | 
            -
                  if options. | 
| 479 | 
            +
                  if options.key?(:timeout)
         | 
| 439 480 | 
             
                    options[:connect_timeout] ||= options[:timeout]
         | 
| 440 481 | 
             
                    options[:read_timeout]    ||= options[:timeout]
         | 
| 441 482 | 
             
                    options[:write_timeout]   ||= options[:timeout]
         | 
| @@ -445,12 +486,16 @@ class Redis | |
| 445 486 | 
             
                  options[:read_timeout]    = Float(options[:read_timeout])
         | 
| 446 487 | 
             
                  options[:write_timeout]   = Float(options[:write_timeout])
         | 
| 447 488 |  | 
| 489 | 
            +
                  options[:reconnect_attempts] = options[:reconnect_attempts].to_i
         | 
| 490 | 
            +
                  options[:reconnect_delay] = options[:reconnect_delay].to_f
         | 
| 491 | 
            +
                  options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f
         | 
| 492 | 
            +
             | 
| 448 493 | 
             
                  options[:db] = options[:db].to_i
         | 
| 449 494 | 
             
                  options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
         | 
| 450 495 |  | 
| 451 496 | 
             
                  case options[:tcp_keepalive]
         | 
| 452 497 | 
             
                  when Hash
         | 
| 453 | 
            -
                    [ | 
| 498 | 
            +
                    %i[time intvl probes].each do |key|
         | 
| 454 499 | 
             
                      unless options[:tcp_keepalive][key].is_a?(Integer)
         | 
| 455 500 | 
             
                        raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
         | 
| 456 501 | 
             
                      end
         | 
| @@ -458,13 +503,13 @@ class Redis | |
| 458 503 |  | 
| 459 504 | 
             
                  when Integer
         | 
| 460 505 | 
             
                    if options[:tcp_keepalive] >= 60
         | 
| 461 | 
            -
                      options[:tcp_keepalive] = {: | 
| 506 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
         | 
| 462 507 |  | 
| 463 508 | 
             
                    elsif options[:tcp_keepalive] >= 30
         | 
| 464 | 
            -
                      options[:tcp_keepalive] = {: | 
| 509 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
         | 
| 465 510 |  | 
| 466 511 | 
             
                    elsif options[:tcp_keepalive] >= 5
         | 
| 467 | 
            -
                      options[:tcp_keepalive] = {: | 
| 512 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
         | 
| 468 513 | 
             
                    end
         | 
| 469 514 | 
             
                  end
         | 
| 470 515 |  | 
| @@ -476,13 +521,18 @@ class Redis | |
| 476 521 | 
             
                def _parse_driver(driver)
         | 
| 477 522 | 
             
                  driver = driver.to_s if driver.is_a?(Symbol)
         | 
| 478 523 |  | 
| 479 | 
            -
                  if driver. | 
| 524 | 
            +
                  if driver.is_a?(String)
         | 
| 480 525 | 
             
                    begin
         | 
| 481 | 
            -
                       | 
| 482 | 
            -
                      driver = Connection.const_get(driver.capitalize)
         | 
| 526 | 
            +
                      require_relative "connection/#{driver}"
         | 
| 483 527 | 
             
                    rescue LoadError, NameError
         | 
| 484 | 
            -
                       | 
| 528 | 
            +
                      begin
         | 
| 529 | 
            +
                        require "redis/connection/#{driver}"
         | 
| 530 | 
            +
                      rescue LoadError, NameError => error
         | 
| 531 | 
            +
                        raise "Cannot load driver #{driver.inspect}: #{error.message}"
         | 
| 532 | 
            +
                      end
         | 
| 485 533 | 
             
                    end
         | 
| 534 | 
            +
             | 
| 535 | 
            +
                    driver = Connection.const_get(driver.capitalize)
         | 
| 486 536 | 
             
                  end
         | 
| 487 537 |  | 
| 488 538 | 
             
                  driver
         | 
| @@ -497,18 +547,16 @@ class Redis | |
| 497 547 | 
             
                    @options
         | 
| 498 548 | 
             
                  end
         | 
| 499 549 |  | 
| 500 | 
            -
                  def check(client)
         | 
| 501 | 
            -
                  end
         | 
| 550 | 
            +
                  def check(client); end
         | 
| 502 551 |  | 
| 503 552 | 
             
                  class Sentinel < Connector
         | 
| 504 553 | 
             
                    def initialize(options)
         | 
| 505 554 | 
             
                      super(options)
         | 
| 506 555 |  | 
| 507 | 
            -
                      @options[:password] = DEFAULTS.fetch(:password)
         | 
| 508 556 | 
             
                      @options[:db] = DEFAULTS.fetch(:db)
         | 
| 509 557 |  | 
| 510 558 | 
             
                      @sentinels = @options.delete(:sentinels).dup
         | 
| 511 | 
            -
                      @role = @options | 
| 559 | 
            +
                      @role = (@options[:role] || "master").to_s
         | 
| 512 560 | 
             
                      @master = @options[:host]
         | 
| 513 561 | 
             
                    end
         | 
| 514 562 |  | 
| @@ -531,13 +579,13 @@ class Redis | |
| 531 579 |  | 
| 532 580 | 
             
                    def resolve
         | 
| 533 581 | 
             
                      result = case @role
         | 
| 534 | 
            -
             | 
| 535 | 
            -
             | 
| 536 | 
            -
             | 
| 537 | 
            -
             | 
| 538 | 
            -
             | 
| 539 | 
            -
             | 
| 540 | 
            -
             | 
| 582 | 
            +
                      when "master"
         | 
| 583 | 
            +
                        resolve_master
         | 
| 584 | 
            +
                      when "slave"
         | 
| 585 | 
            +
                        resolve_slave
         | 
| 586 | 
            +
                      else
         | 
| 587 | 
            +
                        raise ArgumentError, "Unknown instance role #{@role}"
         | 
| 588 | 
            +
                      end
         | 
| 541 589 |  | 
| 542 590 | 
             
                      result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
         | 
| 543 591 | 
             
                    end
         | 
| @@ -545,10 +593,12 @@ class Redis | |
| 545 593 | 
             
                    def sentinel_detect
         | 
| 546 594 | 
             
                      @sentinels.each do |sentinel|
         | 
| 547 595 | 
             
                        client = Client.new(@options.merge({
         | 
| 548 | 
            -
             | 
| 549 | 
            -
             | 
| 550 | 
            -
             | 
| 551 | 
            -
             | 
| 596 | 
            +
                                                             host: sentinel[:host] || sentinel["host"],
         | 
| 597 | 
            +
                                                             port: sentinel[:port] || sentinel["port"],
         | 
| 598 | 
            +
                                                             username: sentinel[:username] || sentinel["username"],
         | 
| 599 | 
            +
                                                             password: sentinel[:password] || sentinel["password"],
         | 
| 600 | 
            +
                                                             reconnect_attempts: 0
         | 
| 601 | 
            +
                                                           }))
         | 
| 552 602 |  | 
| 553 603 | 
             
                        begin
         | 
| 554 604 | 
             
                          if result = yield(client)
         | 
| @@ -570,7 +620,7 @@ class Redis | |
| 570 620 | 
             
                    def resolve_master
         | 
| 571 621 | 
             
                      sentinel_detect do |client|
         | 
| 572 622 | 
             
                        if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
         | 
| 573 | 
            -
                          {: | 
| 623 | 
            +
                          { host: reply[0], port: reply[1] }
         | 
| 574 624 | 
             
                        end
         | 
| 575 625 | 
             
                      end
         | 
| 576 626 | 
             
                    end
         | 
| @@ -578,9 +628,19 @@ class Redis | |
| 578 628 | 
             
                    def resolve_slave
         | 
| 579 629 | 
             
                      sentinel_detect do |client|
         | 
| 580 630 | 
             
                        if reply = client.call(["sentinel", "slaves", @master])
         | 
| 581 | 
            -
                           | 
| 582 | 
            -
             | 
| 583 | 
            -
                          { | 
| 631 | 
            +
                          slaves = reply.map { |s| s.each_slice(2).to_h }
         | 
| 632 | 
            +
                          slaves.each { |s| s['flags'] = s.fetch('flags').split(',') }
         | 
| 633 | 
            +
                          slaves.reject! { |s| s.fetch('flags').include?('s_down') }
         | 
| 634 | 
            +
             | 
| 635 | 
            +
                          if slaves.empty?
         | 
| 636 | 
            +
                            raise CannotConnectError, 'No slaves available.'
         | 
| 637 | 
            +
                          else
         | 
| 638 | 
            +
                            slave = slaves.sample
         | 
| 639 | 
            +
                            {
         | 
| 640 | 
            +
                              host: slave.fetch('ip'),
         | 
| 641 | 
            +
                              port: slave.fetch('port')
         | 
| 642 | 
            +
                            }
         | 
| 643 | 
            +
                          end
         | 
| 584 644 | 
             
                        end
         | 
| 585 645 | 
             
                      end
         | 
| 586 646 | 
             
                    end
         |