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
| @@ -1,12 +1,13 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "registry"
         | 
| 4 | 
            +
            require_relative "../errors"
         | 
| 3 5 | 
             
            require "hiredis/connection"
         | 
| 4 6 | 
             
            require "timeout"
         | 
| 5 7 |  | 
| 6 8 | 
             
            class Redis
         | 
| 7 9 | 
             
              module Connection
         | 
| 8 10 | 
             
                class Hiredis
         | 
| 9 | 
            -
             | 
| 10 11 | 
             
                  def self.connect(config)
         | 
| 11 12 | 
             
                    connection = ::Hiredis::Connection.new
         | 
| 12 13 | 
             
                    connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
         | 
| @@ -31,7 +32,7 @@ class Redis | |
| 31 32 | 
             
                  end
         | 
| 32 33 |  | 
| 33 34 | 
             
                  def connected?
         | 
| 34 | 
            -
                    @connection | 
| 35 | 
            +
                    @connection&.connected?
         | 
| 35 36 | 
             
                  end
         | 
| 36 37 |  | 
| 37 38 | 
             
                  def timeout=(timeout)
         | 
| @@ -57,7 +58,7 @@ class Redis | |
| 57 58 | 
             
                  rescue Errno::EAGAIN
         | 
| 58 59 | 
             
                    raise TimeoutError
         | 
| 59 60 | 
             
                  rescue RuntimeError => err
         | 
| 60 | 
            -
                    raise ProtocolError | 
| 61 | 
            +
                    raise ProtocolError, err.message
         | 
| 61 62 | 
             
                  end
         | 
| 62 63 | 
             
                end
         | 
| 63 64 | 
             
              end
         | 
| @@ -1,6 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            class Redis
         | 
| 2 4 | 
             
              module Connection
         | 
| 3 | 
            -
             | 
| 4 5 | 
             
                # Store a list of loaded connection drivers in the Connection module.
         | 
| 5 6 | 
             
                # Redis::Client uses the last required driver by default, and will be aware
         | 
| 6 7 | 
             
                # of the loaded connection drivers if the user chooses to override the
         | 
| @@ -1,6 +1,8 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "registry"
         | 
| 4 | 
            +
            require_relative "command_helper"
         | 
| 5 | 
            +
            require_relative "../errors"
         | 
| 4 6 | 
             
            require "socket"
         | 
| 5 7 | 
             
            require "timeout"
         | 
| 6 8 |  | 
| @@ -10,130 +12,87 @@ rescue LoadError | |
| 10 12 | 
             
              # Not all systems have OpenSSL support
         | 
| 11 13 | 
             
            end
         | 
| 12 14 |  | 
| 13 | 
            -
            if RUBY_VERSION < "1.9.3"
         | 
| 14 | 
            -
              class String
         | 
| 15 | 
            -
                # Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
         | 
| 16 | 
            -
                # We can simply slice the string, which is a byte array there.
         | 
| 17 | 
            -
                def byteslice(*args)
         | 
| 18 | 
            -
                  slice(*args)
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
              end
         | 
| 21 | 
            -
            end
         | 
| 22 | 
            -
             | 
| 23 15 | 
             
            class Redis
         | 
| 24 16 | 
             
              module Connection
         | 
| 25 17 | 
             
                module SocketMixin
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  CRLF = "\r\n".freeze
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  # Exceptions raised during non-blocking I/O ops that require retrying the op
         | 
| 30 | 
            -
                  if RUBY_VERSION >= "1.9.3"
         | 
| 31 | 
            -
                    NBIO_READ_EXCEPTIONS = [IO::WaitReadable]
         | 
| 32 | 
            -
                    NBIO_WRITE_EXCEPTIONS = [IO::WaitWritable]
         | 
| 33 | 
            -
                  else
         | 
| 34 | 
            -
                    NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
         | 
| 35 | 
            -
                    NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
         | 
| 36 | 
            -
                  end
         | 
| 18 | 
            +
                  CRLF = "\r\n"
         | 
| 37 19 |  | 
| 38 20 | 
             
                  def initialize(*args)
         | 
| 39 21 | 
             
                    super(*args)
         | 
| 40 22 |  | 
| 41 23 | 
             
                    @timeout = @write_timeout = nil
         | 
| 42 | 
            -
                    @buffer = ""
         | 
| 24 | 
            +
                    @buffer = "".dup
         | 
| 43 25 | 
             
                  end
         | 
| 44 26 |  | 
| 45 27 | 
             
                  def timeout=(timeout)
         | 
| 46 | 
            -
                    if timeout && timeout > 0
         | 
| 47 | 
            -
                      @timeout = timeout
         | 
| 48 | 
            -
                    else
         | 
| 49 | 
            -
                      @timeout = nil
         | 
| 50 | 
            -
                    end
         | 
| 28 | 
            +
                    @timeout = (timeout if timeout && timeout > 0)
         | 
| 51 29 | 
             
                  end
         | 
| 52 30 |  | 
| 53 31 | 
             
                  def write_timeout=(timeout)
         | 
| 54 | 
            -
                    if timeout && timeout > 0
         | 
| 55 | 
            -
                      @write_timeout = timeout
         | 
| 56 | 
            -
                    else
         | 
| 57 | 
            -
                      @write_timeout = nil
         | 
| 58 | 
            -
                    end
         | 
| 32 | 
            +
                    @write_timeout = (timeout if timeout && timeout > 0)
         | 
| 59 33 | 
             
                  end
         | 
| 60 34 |  | 
| 61 35 | 
             
                  def read(nbytes)
         | 
| 62 36 | 
             
                    result = @buffer.slice!(0, nbytes)
         | 
| 63 37 |  | 
| 64 | 
            -
                    while result.bytesize < nbytes
         | 
| 65 | 
            -
                      result << _read_from_socket(nbytes - result.bytesize)
         | 
| 66 | 
            -
                    end
         | 
| 38 | 
            +
                    result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
         | 
| 67 39 |  | 
| 68 40 | 
             
                    result
         | 
| 69 41 | 
             
                  end
         | 
| 70 42 |  | 
| 71 43 | 
             
                  def gets
         | 
| 72 | 
            -
                    crlf = nil
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                    while (crlf = @buffer.index(CRLF)) == nil
         | 
| 75 | 
            -
                      @buffer << _read_from_socket(1024)
         | 
| 44 | 
            +
                    while (crlf = @buffer.index(CRLF)).nil?
         | 
| 45 | 
            +
                      @buffer << _read_from_socket(16_384)
         | 
| 76 46 | 
             
                    end
         | 
| 77 47 |  | 
| 78 48 | 
             
                    @buffer.slice!(0, crlf + CRLF.bytesize)
         | 
| 79 49 | 
             
                  end
         | 
| 80 50 |  | 
| 81 51 | 
             
                  def _read_from_socket(nbytes)
         | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                       | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
                        raise Redis::TimeoutError
         | 
| 97 | 
            -
                      end
         | 
| 98 | 
            -
                    end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                  rescue EOFError
         | 
| 101 | 
            -
                    raise Errno::ECONNRESET
         | 
| 102 | 
            -
                  end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                  def _write_to_socket(data)
         | 
| 105 | 
            -
                    begin
         | 
| 106 | 
            -
                      write_nonblock(data)
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                    rescue *NBIO_WRITE_EXCEPTIONS
         | 
| 109 | 
            -
                      if IO.select(nil, [self], nil, @write_timeout)
         | 
| 110 | 
            -
                        retry
         | 
| 111 | 
            -
                      else
         | 
| 112 | 
            -
                        raise Redis::TimeoutError
         | 
| 113 | 
            -
                      end
         | 
| 114 | 
            -
                    rescue *NBIO_READ_EXCEPTIONS
         | 
| 115 | 
            -
                      if IO.select([self], nil, nil, @write_timeout)
         | 
| 116 | 
            -
                        retry
         | 
| 117 | 
            -
                      else
         | 
| 118 | 
            -
                        raise Redis::TimeoutError
         | 
| 52 | 
            +
                    loop do
         | 
| 53 | 
            +
                      case chunk = read_nonblock(nbytes, exception: false)
         | 
| 54 | 
            +
                      when :wait_readable
         | 
| 55 | 
            +
                        unless wait_readable(@timeout)
         | 
| 56 | 
            +
                          raise Redis::TimeoutError
         | 
| 57 | 
            +
                        end
         | 
| 58 | 
            +
                      when :wait_writable
         | 
| 59 | 
            +
                        unless wait_writable(@timeout)
         | 
| 60 | 
            +
                          raise Redis::TimeoutError
         | 
| 61 | 
            +
                        end
         | 
| 62 | 
            +
                      when nil
         | 
| 63 | 
            +
                        raise Errno::ECONNRESET
         | 
| 64 | 
            +
                      when String
         | 
| 65 | 
            +
                        return chunk
         | 
| 119 66 | 
             
                      end
         | 
| 120 67 | 
             
                    end
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                  rescue EOFError
         | 
| 123 | 
            -
                    raise Errno::ECONNRESET
         | 
| 124 68 | 
             
                  end
         | 
| 125 69 |  | 
| 126 | 
            -
                  def write( | 
| 127 | 
            -
                    return super( | 
| 70 | 
            +
                  def write(buffer)
         | 
| 71 | 
            +
                    return super(buffer) unless @write_timeout
         | 
| 128 72 |  | 
| 129 | 
            -
                     | 
| 130 | 
            -
                     | 
| 73 | 
            +
                    bytes_to_write = buffer.bytesize
         | 
| 74 | 
            +
                    total_bytes_written = 0
         | 
| 131 75 | 
             
                    loop do
         | 
| 132 | 
            -
                       | 
| 76 | 
            +
                      case bytes_written = write_nonblock(buffer, exception: false)
         | 
| 77 | 
            +
                      when :wait_readable
         | 
| 78 | 
            +
                        unless wait_readable(@write_timeout)
         | 
| 79 | 
            +
                          raise Redis::TimeoutError
         | 
| 80 | 
            +
                        end
         | 
| 81 | 
            +
                      when :wait_writable
         | 
| 82 | 
            +
                        unless wait_writable(@write_timeout)
         | 
| 83 | 
            +
                          raise Redis::TimeoutError
         | 
| 84 | 
            +
                        end
         | 
| 85 | 
            +
                      when nil
         | 
| 86 | 
            +
                        raise Errno::ECONNRESET
         | 
| 87 | 
            +
                      when Integer
         | 
| 88 | 
            +
                        total_bytes_written += bytes_written
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                        if total_bytes_written >= bytes_to_write
         | 
| 91 | 
            +
                          return total_bytes_written
         | 
| 92 | 
            +
                        end
         | 
| 133 93 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
                       | 
| 136 | 
            -
                      data = data.byteslice(count..-1)
         | 
| 94 | 
            +
                        buffer = buffer.byteslice(bytes_written..-1)
         | 
| 95 | 
            +
                      end
         | 
| 137 96 | 
             
                    end
         | 
| 138 97 | 
             
                  end
         | 
| 139 98 | 
             
                end
         | 
| @@ -143,7 +102,6 @@ class Redis | |
| 143 102 | 
             
                  require "timeout"
         | 
| 144 103 |  | 
| 145 104 | 
             
                  class TCPSocket < ::TCPSocket
         | 
| 146 | 
            -
             | 
| 147 105 | 
             
                    include SocketMixin
         | 
| 148 106 |  | 
| 149 107 | 
             
                    def self.connect(host, port, timeout)
         | 
| @@ -159,7 +117,6 @@ class Redis | |
| 159 117 | 
             
                  if defined?(::UNIXSocket)
         | 
| 160 118 |  | 
| 161 119 | 
             
                    class UNIXSocket < ::UNIXSocket
         | 
| 162 | 
            -
             | 
| 163 120 | 
             
                      include SocketMixin
         | 
| 164 121 |  | 
| 165 122 | 
             
                      def self.connect(path, timeout)
         | 
| @@ -171,13 +128,12 @@ class Redis | |
| 171 128 | 
             
                        raise TimeoutError
         | 
| 172 129 | 
             
                      end
         | 
| 173 130 |  | 
| 174 | 
            -
                      # JRuby raises Errno::EAGAIN on #read_nonblock even when  | 
| 131 | 
            +
                      # JRuby raises Errno::EAGAIN on #read_nonblock even when it
         | 
| 175 132 | 
             
                      # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
         | 
| 176 133 | 
             
                      # Use the blocking #readpartial method instead.
         | 
| 177 134 |  | 
| 178 135 | 
             
                      def _read_from_socket(nbytes)
         | 
| 179 136 | 
             
                        readpartial(nbytes)
         | 
| 180 | 
            -
             | 
| 181 137 | 
             
                      rescue EOFError
         | 
| 182 138 | 
             
                        raise Errno::ECONNRESET
         | 
| 183 139 | 
             
                      end
         | 
| @@ -188,19 +144,16 @@ class Redis | |
| 188 144 | 
             
                else
         | 
| 189 145 |  | 
| 190 146 | 
             
                  class TCPSocket < ::Socket
         | 
| 191 | 
            -
             | 
| 192 147 | 
             
                    include SocketMixin
         | 
| 193 148 |  | 
| 194 | 
            -
                    def self.connect_addrinfo( | 
| 195 | 
            -
                      sock = new(::Socket.const_get( | 
| 196 | 
            -
                      sockaddr = ::Socket.pack_sockaddr_in(port,  | 
| 149 | 
            +
                    def self.connect_addrinfo(addrinfo, port, timeout)
         | 
| 150 | 
            +
                      sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
         | 
| 151 | 
            +
                      sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
         | 
| 197 152 |  | 
| 198 153 | 
             
                      begin
         | 
| 199 154 | 
             
                        sock.connect_nonblock(sockaddr)
         | 
| 200 155 | 
             
                      rescue Errno::EINPROGRESS
         | 
| 201 | 
            -
                         | 
| 202 | 
            -
                          raise TimeoutError
         | 
| 203 | 
            -
                        end
         | 
| 156 | 
            +
                        raise TimeoutError unless sock.wait_writable(timeout)
         | 
| 204 157 |  | 
| 205 158 | 
             
                        begin
         | 
| 206 159 | 
             
                          sock.connect_nonblock(sockaddr)
         | 
| @@ -239,14 +192,13 @@ class Redis | |
| 239 192 | 
             
                          return connect_addrinfo(ai, port, timeout)
         | 
| 240 193 | 
             
                        rescue SystemCallError
         | 
| 241 194 | 
             
                          # Raise if this was our last attempt.
         | 
| 242 | 
            -
                          raise if addrinfo.length == i+1
         | 
| 195 | 
            +
                          raise if addrinfo.length == i + 1
         | 
| 243 196 | 
             
                        end
         | 
| 244 197 | 
             
                      end
         | 
| 245 198 | 
             
                    end
         | 
| 246 199 | 
             
                  end
         | 
| 247 200 |  | 
| 248 201 | 
             
                  class UNIXSocket < ::Socket
         | 
| 249 | 
            -
             | 
| 250 202 | 
             
                    include SocketMixin
         | 
| 251 203 |  | 
| 252 204 | 
             
                    def self.connect(path, timeout)
         | 
| @@ -256,9 +208,7 @@ class Redis | |
| 256 208 | 
             
                      begin
         | 
| 257 209 | 
             
                        sock.connect_nonblock(sockaddr)
         | 
| 258 210 | 
             
                      rescue Errno::EINPROGRESS
         | 
| 259 | 
            -
                         | 
| 260 | 
            -
                          raise TimeoutError
         | 
| 261 | 
            -
                        end
         | 
| 211 | 
            +
                        raise TimeoutError unless sock.wait_writable(timeout)
         | 
| 262 212 |  | 
| 263 213 | 
             
                        begin
         | 
| 264 214 | 
             
                          sock.connect_nonblock(sockaddr)
         | 
| @@ -276,17 +226,58 @@ class Redis | |
| 276 226 | 
             
                  class SSLSocket < ::OpenSSL::SSL::SSLSocket
         | 
| 277 227 | 
             
                    include SocketMixin
         | 
| 278 228 |  | 
| 229 | 
            +
                    unless method_defined?(:wait_readable)
         | 
| 230 | 
            +
                      def wait_readable(timeout = nil)
         | 
| 231 | 
            +
                        to_io.wait_readable(timeout)
         | 
| 232 | 
            +
                      end
         | 
| 233 | 
            +
                    end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                    unless method_defined?(:wait_writable)
         | 
| 236 | 
            +
                      def wait_writable(timeout = nil)
         | 
| 237 | 
            +
                        to_io.wait_writable(timeout)
         | 
| 238 | 
            +
                      end
         | 
| 239 | 
            +
                    end
         | 
| 240 | 
            +
             | 
| 279 241 | 
             
                    def self.connect(host, port, timeout, ssl_params)
         | 
| 280 242 | 
             
                      # Note: this is using Redis::Connection::TCPSocket
         | 
| 281 243 | 
             
                      tcp_sock = TCPSocket.connect(host, port, timeout)
         | 
| 282 244 |  | 
| 283 245 | 
             
                      ctx = OpenSSL::SSL::SSLContext.new
         | 
| 284 | 
            -
             | 
| 246 | 
            +
             | 
| 247 | 
            +
                      # The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
         | 
| 248 | 
            +
                      ctx.set_params(ssl_params || {})
         | 
| 285 249 |  | 
| 286 250 | 
             
                      ssl_sock = new(tcp_sock, ctx)
         | 
| 287 251 | 
             
                      ssl_sock.hostname = host
         | 
| 288 | 
            -
             | 
| 289 | 
            -
                       | 
| 252 | 
            +
             | 
| 253 | 
            +
                      begin
         | 
| 254 | 
            +
                        # Initiate the socket connection in the background. If it doesn't fail
         | 
| 255 | 
            +
                        # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
         | 
| 256 | 
            +
                        # indicating the connection is in progress.
         | 
| 257 | 
            +
                        # Unlike waiting for a tcp socket to connect, you can't time out ssl socket
         | 
| 258 | 
            +
                        # connections during the connect phase properly, because IO.select only partially works.
         | 
| 259 | 
            +
                        # Instead, you have to retry.
         | 
| 260 | 
            +
                        ssl_sock.connect_nonblock
         | 
| 261 | 
            +
                      rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
         | 
| 262 | 
            +
                        if ssl_sock.wait_readable(timeout)
         | 
| 263 | 
            +
                          retry
         | 
| 264 | 
            +
                        else
         | 
| 265 | 
            +
                          raise TimeoutError
         | 
| 266 | 
            +
                        end
         | 
| 267 | 
            +
                      rescue IO::WaitWritable
         | 
| 268 | 
            +
                        if ssl_sock.wait_writable(timeout)
         | 
| 269 | 
            +
                          retry
         | 
| 270 | 
            +
                        else
         | 
| 271 | 
            +
                          raise TimeoutError
         | 
| 272 | 
            +
                        end
         | 
| 273 | 
            +
                      end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                      unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
         | 
| 276 | 
            +
                        ctx.respond_to?(:verify_hostname) &&
         | 
| 277 | 
            +
                        !ctx.verify_hostname
         | 
| 278 | 
            +
                      )
         | 
| 279 | 
            +
                        ssl_sock.post_connection_check(host)
         | 
| 280 | 
            +
                      end
         | 
| 290 281 |  | 
| 291 282 | 
             
                      ssl_sock
         | 
| 292 283 | 
             
                    end
         | 
| @@ -296,31 +287,32 @@ class Redis | |
| 296 287 | 
             
                class Ruby
         | 
| 297 288 | 
             
                  include Redis::Connection::CommandHelper
         | 
| 298 289 |  | 
| 299 | 
            -
                  MINUS    = "-" | 
| 300 | 
            -
                  PLUS     = "+" | 
| 301 | 
            -
                  COLON    = ":" | 
| 302 | 
            -
                  DOLLAR   = "$" | 
| 303 | 
            -
                  ASTERISK = "*" | 
| 290 | 
            +
                  MINUS    = "-"
         | 
| 291 | 
            +
                  PLUS     = "+"
         | 
| 292 | 
            +
                  COLON    = ":"
         | 
| 293 | 
            +
                  DOLLAR   = "$"
         | 
| 294 | 
            +
                  ASTERISK = "*"
         | 
| 304 295 |  | 
| 305 296 | 
             
                  def self.connect(config)
         | 
| 306 297 | 
             
                    if config[:scheme] == "unix"
         | 
| 307 298 | 
             
                      raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
         | 
| 299 | 
            +
             | 
| 308 300 | 
             
                      sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
         | 
| 309 301 | 
             
                    elsif config[:scheme] == "rediss" || config[:ssl]
         | 
| 310 | 
            -
                      raise ArgumentError, "This library does not support SSL on Ruby < 1.9" if RUBY_VERSION < "1.9.3"
         | 
| 311 302 | 
             
                      sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
         | 
| 312 303 | 
             
                    else
         | 
| 313 304 | 
             
                      sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
         | 
| 314 305 | 
             
                    end
         | 
| 315 306 |  | 
| 316 307 | 
             
                    instance = new(sock)
         | 
| 317 | 
            -
                    instance.timeout = config[: | 
| 308 | 
            +
                    instance.timeout = config[:read_timeout]
         | 
| 318 309 | 
             
                    instance.write_timeout = config[:write_timeout]
         | 
| 319 310 | 
             
                    instance.set_tcp_keepalive config[:tcp_keepalive]
         | 
| 311 | 
            +
                    instance.set_tcp_nodelay if sock.is_a? TCPSocket
         | 
| 320 312 | 
             
                    instance
         | 
| 321 313 | 
             
                  end
         | 
| 322 314 |  | 
| 323 | 
            -
                  if [ | 
| 315 | 
            +
                  if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
         | 
| 324 316 | 
             
                    def set_tcp_keepalive(keepalive)
         | 
| 325 317 | 
             
                      return unless keepalive.is_a?(Hash)
         | 
| 326 318 |  | 
| @@ -332,14 +324,13 @@ class Redis | |
| 332 324 |  | 
| 333 325 | 
             
                    def get_tcp_keepalive
         | 
| 334 326 | 
             
                      {
         | 
| 335 | 
            -
                        : | 
| 336 | 
            -
                        : | 
| 337 | 
            -
                        : | 
| 327 | 
            +
                        time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
         | 
| 328 | 
            +
                        intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
         | 
| 329 | 
            +
                        probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
         | 
| 338 330 | 
             
                      }
         | 
| 339 331 | 
             
                    end
         | 
| 340 332 | 
             
                  else
         | 
| 341 | 
            -
                    def set_tcp_keepalive(keepalive)
         | 
| 342 | 
            -
                    end
         | 
| 333 | 
            +
                    def set_tcp_keepalive(keepalive); end
         | 
| 343 334 |  | 
| 344 335 | 
             
                    def get_tcp_keepalive
         | 
| 345 336 | 
             
                      {
         | 
| @@ -347,12 +338,21 @@ class Redis | |
| 347 338 | 
             
                    end
         | 
| 348 339 | 
             
                  end
         | 
| 349 340 |  | 
| 341 | 
            +
                  # disables Nagle's Algorithm, prevents multiple round trips with MULTI
         | 
| 342 | 
            +
                  if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
         | 
| 343 | 
            +
                    def set_tcp_nodelay
         | 
| 344 | 
            +
                      @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
         | 
| 345 | 
            +
                    end
         | 
| 346 | 
            +
                  else
         | 
| 347 | 
            +
                    def set_tcp_nodelay; end
         | 
| 348 | 
            +
                  end
         | 
| 349 | 
            +
             | 
| 350 350 | 
             
                  def initialize(sock)
         | 
| 351 351 | 
             
                    @sock = sock
         | 
| 352 352 | 
             
                  end
         | 
| 353 353 |  | 
| 354 354 | 
             
                  def connected?
         | 
| 355 | 
            -
                     | 
| 355 | 
            +
                    !!@sock
         | 
| 356 356 | 
             
                  end
         | 
| 357 357 |  | 
| 358 358 | 
             
                  def disconnect
         | 
| @@ -363,9 +363,7 @@ class Redis | |
| 363 363 | 
             
                  end
         | 
| 364 364 |  | 
| 365 365 | 
             
                  def timeout=(timeout)
         | 
| 366 | 
            -
                    if @sock.respond_to?(:timeout=)
         | 
| 367 | 
            -
                      @sock.timeout = timeout
         | 
| 368 | 
            -
                    end
         | 
| 366 | 
            +
                    @sock.timeout = timeout if @sock.respond_to?(:timeout=)
         | 
| 369 367 | 
             
                  end
         | 
| 370 368 |  | 
| 371 369 | 
             
                  def write_timeout=(timeout)
         | 
| @@ -380,7 +378,6 @@ class Redis | |
| 380 378 | 
             
                    line = @sock.gets
         | 
| 381 379 | 
             
                    reply_type = line.slice!(0, 1)
         | 
| 382 380 | 
             
                    format_reply(reply_type, line)
         | 
| 383 | 
            -
             | 
| 384 381 | 
             
                  rescue Errno::EAGAIN
         | 
| 385 382 | 
             
                    raise TimeoutError
         | 
| 386 383 | 
             
                  end
         | 
| @@ -392,7 +389,7 @@ class Redis | |
| 392 389 | 
             
                    when COLON    then format_integer_reply(line)
         | 
| 393 390 | 
             
                    when DOLLAR   then format_bulk_reply(line)
         | 
| 394 391 | 
             
                    when ASTERISK then format_multi_bulk_reply(line)
         | 
| 395 | 
            -
                    else raise ProtocolError | 
| 392 | 
            +
                    else raise ProtocolError, reply_type
         | 
| 396 393 | 
             
                    end
         | 
| 397 394 | 
             
                  end
         | 
| 398 395 |  | 
| @@ -411,6 +408,7 @@ class Redis | |
| 411 408 | 
             
                  def format_bulk_reply(line)
         | 
| 412 409 | 
             
                    bulklen = line.to_i
         | 
| 413 410 | 
             
                    return if bulklen == -1
         | 
| 411 | 
            +
             | 
| 414 412 | 
             
                    reply = encode(@sock.read(bulklen))
         | 
| 415 413 | 
             
                    @sock.read(2) # Discard CRLF.
         | 
| 416 414 | 
             
                    reply
         |