dalli 2.7.11 → 3.1.5
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.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +7 -6
- data/History.md +111 -0
- data/README.md +26 -200
- data/lib/dalli/cas/client.rb +1 -57
- data/lib/dalli/client.rb +226 -263
- data/lib/dalli/compressor.rb +12 -2
- data/lib/dalli/key_manager.rb +113 -0
- data/lib/dalli/options.rb +6 -7
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/base.rb +234 -0
- data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
- data/lib/dalli/protocol/binary/response_header.rb +36 -0
- data/lib/dalli/protocol/binary/response_processor.rb +239 -0
- data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
- data/lib/dalli/protocol/binary.rb +180 -0
- data/lib/dalli/protocol/connection_manager.rb +242 -0
- data/lib/dalli/protocol/response_buffer.rb +53 -0
- data/lib/dalli/protocol/server_config_parser.rb +84 -0
- data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
- data/lib/dalli/protocol/value_compressor.rb +85 -0
- data/lib/dalli/protocol/value_marshaller.rb +59 -0
- data/lib/dalli/protocol/value_serializer.rb +91 -0
- data/lib/dalli/protocol.rb +8 -0
- data/lib/dalli/ring.rb +90 -81
- data/lib/dalli/server.rb +3 -749
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +117 -137
- data/lib/dalli/version.rb +4 -1
- data/lib/dalli.rb +41 -14
- data/lib/rack/session/dalli.rb +95 -95
- metadata +114 -10
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
- data/lib/active_support/cache/dalli_store.rb +0 -441
- data/lib/dalli/railtie.rb +0 -8
    
        data/lib/dalli/client.rb
    CHANGED
    
    | @@ -1,17 +1,24 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require 'digest/md5'
         | 
| 3 4 | 
             
            require 'set'
         | 
| 4 5 |  | 
| 5 6 | 
             
            # encoding: ascii
         | 
| 6 7 | 
             
            module Dalli
         | 
| 8 | 
            +
              ##
         | 
| 9 | 
            +
              # Dalli::Client is the main class which developers will use to interact with
         | 
| 10 | 
            +
              # Memcached.
         | 
| 11 | 
            +
              ##
         | 
| 7 12 | 
             
              class Client
         | 
| 8 | 
            -
             | 
| 9 13 | 
             
                ##
         | 
| 10 14 | 
             
                # Dalli::Client is the main class which developers will use to interact with
         | 
| 11 15 | 
             
                # the memcached server.  Usage:
         | 
| 12 16 | 
             
                #
         | 
| 13 | 
            -
                #   Dalli::Client.new(['localhost:11211:10', | 
| 14 | 
            -
                # | 
| 17 | 
            +
                #   Dalli::Client.new(['localhost:11211:10',
         | 
| 18 | 
            +
                #                      'cache-2.example.com:11211:5',
         | 
| 19 | 
            +
                #                      '192.168.0.1:22122:5',
         | 
| 20 | 
            +
                #                      '/var/run/memcached/socket'],
         | 
| 21 | 
            +
                #                     failover: true, expires_in: 300)
         | 
| 15 22 | 
             
                #
         | 
| 16 23 | 
             
                # servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
         | 
| 17 24 | 
             
                # Both weight and port are optional.  If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
         | 
| @@ -24,16 +31,25 @@ module Dalli | |
| 24 31 | 
             
                # - :namespace - prepend each key with this value to provide simple namespacing.
         | 
| 25 32 | 
             
                # - :failover - if a server is down, look for and store values on another server in the ring.  Default: true.
         | 
| 26 33 | 
             
                # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
         | 
| 27 | 
            -
                # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults | 
| 28 | 
            -
                # | 
| 34 | 
            +
                # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
         | 
| 35 | 
            +
                #                 to 0 or forever.
         | 
| 36 | 
            +
                # - :compress - if true Dalli will compress values larger than compression_min_size bytes before sending them
         | 
| 37 | 
            +
                #               to memcached.  Default: true.
         | 
| 38 | 
            +
                # - :compression_min_size - the minimum size (in bytes) for which Dalli will compress values sent to Memcached.
         | 
| 39 | 
            +
                #                           Defaults to 4K.
         | 
| 29 40 | 
             
                # - :serializer - defaults to Marshal
         | 
| 30 | 
            -
                # - :compressor - defaults to  | 
| 31 | 
            -
                # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for | 
| 32 | 
            -
                #  | 
| 41 | 
            +
                # - :compressor - defaults to Dalli::Compressor, a Zlib-based implementation
         | 
| 42 | 
            +
                # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for
         | 
| 43 | 
            +
                #                 #fetch operations.
         | 
| 44 | 
            +
                # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
         | 
| 45 | 
            +
                #                   useful for injecting a FIPS compliant hash object.
         | 
| 46 | 
            +
                # - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to
         | 
| 47 | 
            +
                #                              pass an alternative implementation using another protocol.
         | 
| 33 48 | 
             
                #
         | 
| 34 | 
            -
                def initialize(servers=nil, options={})
         | 
| 35 | 
            -
                  @servers = normalize_servers(servers | 
| 49 | 
            +
                def initialize(servers = nil, options = {})
         | 
| 50 | 
            +
                  @servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
         | 
| 36 51 | 
             
                  @options = normalize_options(options)
         | 
| 52 | 
            +
                  @key_manager = ::Dalli::KeyManager.new(@options)
         | 
| 37 53 | 
             
                  @ring = nil
         | 
| 38 54 | 
             
                end
         | 
| 39 55 |  | 
| @@ -42,22 +58,37 @@ module Dalli | |
| 42 58 | 
             
                #
         | 
| 43 59 |  | 
| 44 60 | 
             
                ##
         | 
| 45 | 
            -
                #  | 
| 46 | 
            -
                #  | 
| 47 | 
            -
                 | 
| 48 | 
            -
             | 
| 49 | 
            -
                def multi
         | 
| 50 | 
            -
                  old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
         | 
| 51 | 
            -
                  yield
         | 
| 52 | 
            -
                ensure
         | 
| 53 | 
            -
                  Thread.current[:dalli_multi] = old
         | 
| 61 | 
            +
                # Get the value associated with the key.
         | 
| 62 | 
            +
                # If a value is not found, then +nil+ is returned.
         | 
| 63 | 
            +
                def get(key, req_options = nil)
         | 
| 64 | 
            +
                  perform(:get, key, req_options)
         | 
| 54 65 | 
             
                end
         | 
| 55 66 |  | 
| 56 67 | 
             
                ##
         | 
| 57 | 
            -
                #  | 
| 68 | 
            +
                # Gat (get and touch) fetch an item and simultaneously update its expiration time.
         | 
| 69 | 
            +
                #
         | 
| 58 70 | 
             
                # If a value is not found, then +nil+ is returned.
         | 
| 59 | 
            -
                def  | 
| 60 | 
            -
                  perform(: | 
| 71 | 
            +
                def gat(key, ttl = nil)
         | 
| 72 | 
            +
                  perform(:gat, key, ttl_or_default(ttl))
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                ##
         | 
| 76 | 
            +
                # Touch updates expiration time for a given key.
         | 
| 77 | 
            +
                #
         | 
| 78 | 
            +
                # Returns true if key exists, otherwise nil.
         | 
| 79 | 
            +
                def touch(key, ttl = nil)
         | 
| 80 | 
            +
                  resp = perform(:touch, key, ttl_or_default(ttl))
         | 
| 81 | 
            +
                  resp.nil? ? nil : true
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                ##
         | 
| 85 | 
            +
                # Get the value and CAS ID associated with the key.  If a block is provided,
         | 
| 86 | 
            +
                # value and CAS will be passed to the block.
         | 
| 87 | 
            +
                def get_cas(key)
         | 
| 88 | 
            +
                  (value, cas) = perform(:cas, key)
         | 
| 89 | 
            +
                  return [value, cas] unless block_given?
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  yield value, cas
         | 
| 61 92 | 
             
                end
         | 
| 62 93 |  | 
| 63 94 | 
             
                ##
         | 
| @@ -65,20 +96,35 @@ module Dalli | |
| 65 96 | 
             
                # If a block is given, yields key/value pairs one at a time.
         | 
| 66 97 | 
             
                # Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
         | 
| 67 98 | 
             
                def get_multi(*keys)
         | 
| 68 | 
            -
                   | 
| 69 | 
            -
                   | 
| 99 | 
            +
                  keys.flatten!
         | 
| 100 | 
            +
                  keys.compact!
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  return {} if keys.empty?
         | 
| 70 103 |  | 
| 71 | 
            -
                  return {} if check_keys.empty?
         | 
| 72 104 | 
             
                  if block_given?
         | 
| 73 | 
            -
                     | 
| 105 | 
            +
                    pipelined_getter.process(keys) { |k, data| yield k, data.first }
         | 
| 74 106 | 
             
                  else
         | 
| 75 | 
            -
                     | 
| 76 | 
            -
                       | 
| 107 | 
            +
                    {}.tap do |hash|
         | 
| 108 | 
            +
                      pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
         | 
| 77 109 | 
             
                    end
         | 
| 78 110 | 
             
                  end
         | 
| 79 111 | 
             
                end
         | 
| 80 112 |  | 
| 81 | 
            -
                 | 
| 113 | 
            +
                ##
         | 
| 114 | 
            +
                # Fetch multiple keys efficiently, including available metadata such as CAS.
         | 
| 115 | 
            +
                # If a block is given, yields key/data pairs one a time.  Data is an array:
         | 
| 116 | 
            +
                # [value, cas_id]
         | 
| 117 | 
            +
                # If no block is given, returns a hash of
         | 
| 118 | 
            +
                #   { 'key' => [value, cas_id] }
         | 
| 119 | 
            +
                def get_multi_cas(*keys)
         | 
| 120 | 
            +
                  if block_given?
         | 
| 121 | 
            +
                    pipelined_getter.process(keys) { |*args| yield(*args) }
         | 
| 122 | 
            +
                  else
         | 
| 123 | 
            +
                    {}.tap do |hash|
         | 
| 124 | 
            +
                      pipelined_getter.process(keys) { |k, data| hash[k] = data }
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
                end
         | 
| 82 128 |  | 
| 83 129 | 
             
                # Fetch the value associated with the key.
         | 
| 84 130 | 
             
                # If a value is found, then it is returned.
         | 
| @@ -88,17 +134,14 @@ module Dalli | |
| 88 134 | 
             
                # If a value is not found (or if the found value is nil and :cache_nils is false)
         | 
| 89 135 | 
             
                # and a block is given, the block will be invoked and its return value
         | 
| 90 136 | 
             
                # written to the cache and returned.
         | 
| 91 | 
            -
                def fetch(key, ttl=nil,  | 
| 92 | 
            -
                   | 
| 93 | 
            -
                  val = get(key,  | 
| 94 | 
            -
                   | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
                   | 
| 98 | 
            -
             | 
| 99 | 
            -
                    add(key, val, ttl_or_default(ttl), options)
         | 
| 100 | 
            -
                  end
         | 
| 101 | 
            -
                  val
         | 
| 137 | 
            +
                def fetch(key, ttl = nil, req_options = nil)
         | 
| 138 | 
            +
                  req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
         | 
| 139 | 
            +
                  val = get(key, req_options)
         | 
| 140 | 
            +
                  return val unless block_given? && not_found?(val)
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  new_val = yield
         | 
| 143 | 
            +
                  add(key, new_val, ttl_or_default(ttl), req_options)
         | 
| 144 | 
            +
                  new_val
         | 
| 102 145 | 
             
                end
         | 
| 103 146 |  | 
| 104 147 | 
             
                ##
         | 
| @@ -112,8 +155,8 @@ module Dalli | |
| 112 155 | 
             
                # - nil if the key did not exist.
         | 
| 113 156 | 
             
                # - false if the value was changed by someone else.
         | 
| 114 157 | 
             
                # - true if the value was successfully updated.
         | 
| 115 | 
            -
                def cas(key, ttl=nil,  | 
| 116 | 
            -
                  cas_core(key, false, ttl,  | 
| 158 | 
            +
                def cas(key, ttl = nil, req_options = nil, &block)
         | 
| 159 | 
            +
                  cas_core(key, false, ttl, req_options, &block)
         | 
| 117 160 | 
             
                end
         | 
| 118 161 |  | 
| 119 162 | 
             
                ##
         | 
| @@ -123,30 +166,78 @@ module Dalli | |
| 123 166 | 
             
                # Returns:
         | 
| 124 167 | 
             
                # - false if the value was changed by someone else.
         | 
| 125 168 | 
             
                # - true if the value was successfully updated.
         | 
| 126 | 
            -
                def cas!(key, ttl=nil,  | 
| 127 | 
            -
                  cas_core(key, true, ttl,  | 
| 169 | 
            +
                def cas!(key, ttl = nil, req_options = nil, &block)
         | 
| 170 | 
            +
                  cas_core(key, true, ttl, req_options, &block)
         | 
| 128 171 | 
             
                end
         | 
| 129 172 |  | 
| 130 | 
            -
                 | 
| 131 | 
            -
             | 
| 173 | 
            +
                ##
         | 
| 174 | 
            +
                # Turn on quiet aka noreply support for a number of
         | 
| 175 | 
            +
                # memcached operations.
         | 
| 176 | 
            +
                #
         | 
| 177 | 
            +
                # All relevant operations within this block will be effectively
         | 
| 178 | 
            +
                # pipelined as Dalli will use 'quiet' versions.  The invoked methods
         | 
| 179 | 
            +
                # will all return nil, rather than their usual response.  Method
         | 
| 180 | 
            +
                # latency will be substantially lower, as the caller will not be
         | 
| 181 | 
            +
                # blocking on responses.
         | 
| 182 | 
            +
                #
         | 
| 183 | 
            +
                # Currently supports storage (set, add, replace, append, prepend),
         | 
| 184 | 
            +
                # arithmetic (incr, decr), flush and delete operations.  Use of
         | 
| 185 | 
            +
                # unsupported operations inside a block will raise an error.
         | 
| 186 | 
            +
                #
         | 
| 187 | 
            +
                # Any error replies will be discarded at the end of the block, and
         | 
| 188 | 
            +
                # Dalli client methods invoked inside the block will not
         | 
| 189 | 
            +
                # have return values
         | 
| 190 | 
            +
                def quiet
         | 
| 191 | 
            +
                  old = Thread.current[::Dalli::QUIET]
         | 
| 192 | 
            +
                  Thread.current[::Dalli::QUIET] = true
         | 
| 193 | 
            +
                  yield
         | 
| 194 | 
            +
                ensure
         | 
| 195 | 
            +
                  @ring&.pipeline_consume_and_ignore_responses
         | 
| 196 | 
            +
                  Thread.current[::Dalli::QUIET] = old
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
                alias multi quiet
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def set(key, value, ttl = nil, req_options = nil)
         | 
| 201 | 
            +
                  set_cas(key, value, 0, ttl, req_options)
         | 
| 202 | 
            +
                end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                ##
         | 
| 205 | 
            +
                # Set the key-value pair, verifying existing CAS.
         | 
| 206 | 
            +
                # Returns the resulting CAS value if succeeded, and falsy otherwise.
         | 
| 207 | 
            +
                def set_cas(key, value, cas, ttl = nil, req_options = nil)
         | 
| 208 | 
            +
                  perform(:set, key, value, ttl_or_default(ttl), cas, req_options)
         | 
| 132 209 | 
             
                end
         | 
| 133 210 |  | 
| 134 211 | 
             
                ##
         | 
| 135 212 | 
             
                # Conditionally add a key/value pair, if the key does not already exist
         | 
| 136 213 | 
             
                # on the server.  Returns truthy if the operation succeeded.
         | 
| 137 | 
            -
                def add(key, value, ttl=nil,  | 
| 138 | 
            -
                  perform(:add, key, value, ttl_or_default(ttl),  | 
| 214 | 
            +
                def add(key, value, ttl = nil, req_options = nil)
         | 
| 215 | 
            +
                  perform(:add, key, value, ttl_or_default(ttl), req_options)
         | 
| 139 216 | 
             
                end
         | 
| 140 217 |  | 
| 141 218 | 
             
                ##
         | 
| 142 219 | 
             
                # Conditionally add a key/value pair, only if the key already exists
         | 
| 143 220 | 
             
                # on the server.  Returns truthy if the operation succeeded.
         | 
| 144 | 
            -
                def replace(key, value, ttl=nil,  | 
| 145 | 
            -
                   | 
| 221 | 
            +
                def replace(key, value, ttl = nil, req_options = nil)
         | 
| 222 | 
            +
                  replace_cas(key, value, 0, ttl, req_options)
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                ##
         | 
| 226 | 
            +
                # Conditionally add a key/value pair, verifying existing CAS, only if the
         | 
| 227 | 
            +
                # key already exists on the server.  Returns the new CAS value if the
         | 
| 228 | 
            +
                # operation succeeded, or falsy otherwise.
         | 
| 229 | 
            +
                def replace_cas(key, value, cas, ttl = nil, req_options = nil)
         | 
| 230 | 
            +
                  perform(:replace, key, value, ttl_or_default(ttl), cas, req_options)
         | 
| 231 | 
            +
                end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                # Delete a key/value pair, verifying existing CAS.
         | 
| 234 | 
            +
                # Returns true if succeeded, and falsy otherwise.
         | 
| 235 | 
            +
                def delete_cas(key, cas = 0)
         | 
| 236 | 
            +
                  perform(:delete, key, cas)
         | 
| 146 237 | 
             
                end
         | 
| 147 238 |  | 
| 148 239 | 
             
                def delete(key)
         | 
| 149 | 
            -
                   | 
| 240 | 
            +
                  delete_cas(key, 0)
         | 
| 150 241 | 
             
                end
         | 
| 151 242 |  | 
| 152 243 | 
             
                ##
         | 
| @@ -163,13 +254,6 @@ module Dalli | |
| 163 254 | 
             
                  perform(:prepend, key, value.to_s)
         | 
| 164 255 | 
             
                end
         | 
| 165 256 |  | 
| 166 | 
            -
                def flush(delay=0)
         | 
| 167 | 
            -
                  time = -delay
         | 
| 168 | 
            -
                  ring.servers.map { |s| s.request(:flush, time += delay) }
         | 
| 169 | 
            -
                end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
                alias_method :flush_all, :flush
         | 
| 172 | 
            -
             | 
| 173 257 | 
             
                ##
         | 
| 174 258 | 
             
                # Incr adds the given amount to the counter on the memcached server.
         | 
| 175 259 | 
             
                # Amt must be a positive integer value.
         | 
| @@ -181,8 +265,11 @@ module Dalli | |
| 181 265 | 
             
                # Note that the ttl will only apply if the counter does not already
         | 
| 182 266 | 
             
                # exist.  To increase an existing counter and update its TTL, use
         | 
| 183 267 | 
             
                # #cas.
         | 
| 184 | 
            -
                 | 
| 185 | 
            -
             | 
| 268 | 
            +
                #
         | 
| 269 | 
            +
                # If the value already exists, it must have been set with raw: true
         | 
| 270 | 
            +
                def incr(key, amt = 1, ttl = nil, default = nil)
         | 
| 271 | 
            +
                  check_positive!(amt)
         | 
| 272 | 
            +
             | 
| 186 273 | 
             
                  perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
         | 
| 187 274 | 
             
                end
         | 
| 188 275 |  | 
| @@ -200,29 +287,34 @@ module Dalli | |
| 200 287 | 
             
                # Note that the ttl will only apply if the counter does not already
         | 
| 201 288 | 
             
                # exist.  To decrease an existing counter and update its TTL, use
         | 
| 202 289 | 
             
                # #cas.
         | 
| 203 | 
            -
                 | 
| 204 | 
            -
             | 
| 290 | 
            +
                #
         | 
| 291 | 
            +
                # If the value already exists, it must have been set with raw: true
         | 
| 292 | 
            +
                def decr(key, amt = 1, ttl = nil, default = nil)
         | 
| 293 | 
            +
                  check_positive!(amt)
         | 
| 294 | 
            +
             | 
| 205 295 | 
             
                  perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
         | 
| 206 296 | 
             
                end
         | 
| 207 297 |  | 
| 208 298 | 
             
                ##
         | 
| 209 | 
            -
                #  | 
| 210 | 
            -
                #
         | 
| 211 | 
            -
                 | 
| 212 | 
            -
                def  | 
| 213 | 
            -
                   | 
| 214 | 
            -
                  resp.nil? ? nil : true
         | 
| 299 | 
            +
                # Flush the memcached server, at 'delay' seconds in the future.
         | 
| 300 | 
            +
                # Delay defaults to zero seconds, which means an immediate flush.
         | 
| 301 | 
            +
                ##
         | 
| 302 | 
            +
                def flush(delay = 0)
         | 
| 303 | 
            +
                  ring.servers.map { |s| s.request(:flush, delay) }
         | 
| 215 304 | 
             
                end
         | 
| 305 | 
            +
                alias flush_all flush
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
         | 
| 216 308 |  | 
| 217 309 | 
             
                ##
         | 
| 218 310 | 
             
                # Collect the stats for each server.
         | 
| 219 311 | 
             
                # You can optionally pass a type including :items, :slabs or :settings to get specific stats
         | 
| 220 312 | 
             
                # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
         | 
| 221 | 
            -
                def stats(type=nil)
         | 
| 222 | 
            -
                  type = nil  | 
| 313 | 
            +
                def stats(type = nil)
         | 
| 314 | 
            +
                  type = nil unless ALLOWED_STAT_KEYS.include? type
         | 
| 223 315 | 
             
                  values = {}
         | 
| 224 316 | 
             
                  ring.servers.each do |server|
         | 
| 225 | 
            -
                    values[ | 
| 317 | 
            +
                    values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
         | 
| 226 318 | 
             
                  end
         | 
| 227 319 | 
             
                  values
         | 
| 228 320 | 
             
                end
         | 
| @@ -235,32 +327,40 @@ module Dalli | |
| 235 327 | 
             
                  end
         | 
| 236 328 | 
             
                end
         | 
| 237 329 |  | 
| 238 | 
            -
                ##
         | 
| 239 | 
            -
                ## Make sure memcache servers are alive, or raise an Dalli::RingError
         | 
| 240 | 
            -
                def alive!
         | 
| 241 | 
            -
                  ring.server_for_key("")
         | 
| 242 | 
            -
                end
         | 
| 243 | 
            -
             | 
| 244 330 | 
             
                ##
         | 
| 245 331 | 
             
                ## Version of the memcache servers.
         | 
| 246 332 | 
             
                def version
         | 
| 247 333 | 
             
                  values = {}
         | 
| 248 334 | 
             
                  ring.servers.each do |server|
         | 
| 249 | 
            -
                    values[ | 
| 335 | 
            +
                    values[server.name.to_s] = server.alive? ? server.request(:version) : nil
         | 
| 250 336 | 
             
                  end
         | 
| 251 337 | 
             
                  values
         | 
| 252 338 | 
             
                end
         | 
| 253 339 |  | 
| 340 | 
            +
                ##
         | 
| 341 | 
            +
                ## Make sure memcache servers are alive, or raise an Dalli::RingError
         | 
| 342 | 
            +
                def alive!
         | 
| 343 | 
            +
                  ring.server_for_key('')
         | 
| 344 | 
            +
                end
         | 
| 345 | 
            +
             | 
| 254 346 | 
             
                ##
         | 
| 255 347 | 
             
                # Close our connection to each server.
         | 
| 256 348 | 
             
                # If you perform another operation after this, the connections will be re-established.
         | 
| 257 349 | 
             
                def close
         | 
| 258 | 
            -
                   | 
| 259 | 
            -
             | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 350 | 
            +
                  @ring&.close
         | 
| 351 | 
            +
                  @ring = nil
         | 
| 352 | 
            +
                end
         | 
| 353 | 
            +
                alias reset close
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                CACHE_NILS = { cache_nils: true }.freeze
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                def not_found?(val)
         | 
| 358 | 
            +
                  cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
         | 
| 359 | 
            +
                end
         | 
| 360 | 
            +
             | 
| 361 | 
            +
                def cache_nils
         | 
| 362 | 
            +
                  @options[:cache_nils]
         | 
| 262 363 | 
             
                end
         | 
| 263 | 
            -
                alias_method :reset, :close
         | 
| 264 364 |  | 
| 265 365 | 
             
                # Stub method so a bare Dalli client can pretend to be a connection pool.
         | 
| 266 366 | 
             
                def with
         | 
| @@ -269,215 +369,78 @@ module Dalli | |
| 269 369 |  | 
| 270 370 | 
             
                private
         | 
| 271 371 |  | 
| 272 | 
            -
                def  | 
| 372 | 
            +
                def check_positive!(amt)
         | 
| 373 | 
            +
                  raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
         | 
| 374 | 
            +
                end
         | 
| 375 | 
            +
             | 
| 376 | 
            +
                def cas_core(key, always_set, ttl = nil, req_options = nil)
         | 
| 273 377 | 
             
                  (value, cas) = perform(:cas, key)
         | 
| 274 | 
            -
                  value =  | 
| 378 | 
            +
                  value = nil if !value || value == 'Not found'
         | 
| 275 379 | 
             
                  return if value.nil? && !always_set
         | 
| 380 | 
            +
             | 
| 276 381 | 
             
                  newvalue = yield(value)
         | 
| 277 | 
            -
                  perform(:set, key, newvalue, ttl_or_default(ttl), cas,  | 
| 382 | 
            +
                  perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
         | 
| 278 383 | 
             
                end
         | 
| 279 384 |  | 
| 385 | 
            +
                ##
         | 
| 386 | 
            +
                # Uses the argument TTL or the client-wide default.  Ensures
         | 
| 387 | 
            +
                # that the value is an integer
         | 
| 388 | 
            +
                ##
         | 
| 280 389 | 
             
                def ttl_or_default(ttl)
         | 
| 281 390 | 
             
                  (ttl || @options[:expires_in]).to_i
         | 
| 282 391 | 
             
                rescue NoMethodError
         | 
| 283 392 | 
             
                  raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
         | 
| 284 393 | 
             
                end
         | 
| 285 394 |  | 
| 286 | 
            -
                def groups_for_keys(*keys)
         | 
| 287 | 
            -
                  groups = mapped_keys(keys).flatten.group_by do |key|
         | 
| 288 | 
            -
                    begin
         | 
| 289 | 
            -
                      ring.server_for_key(key)
         | 
| 290 | 
            -
                    rescue Dalli::RingError
         | 
| 291 | 
            -
                      Dalli.logger.debug { "unable to get key #{key}" }
         | 
| 292 | 
            -
                      nil
         | 
| 293 | 
            -
                    end
         | 
| 294 | 
            -
                  end
         | 
| 295 | 
            -
                  return groups
         | 
| 296 | 
            -
                end
         | 
| 297 | 
            -
             | 
| 298 | 
            -
                def mapped_keys(keys)
         | 
| 299 | 
            -
                  keys_array = keys.flatten
         | 
| 300 | 
            -
                  keys_array.map! { |a| validate_key(a.to_s) }
         | 
| 301 | 
            -
                  keys_array
         | 
| 302 | 
            -
                end
         | 
| 303 | 
            -
             | 
| 304 | 
            -
                def make_multi_get_requests(groups)
         | 
| 305 | 
            -
                  groups.each do |server, keys_for_server|
         | 
| 306 | 
            -
                    begin
         | 
| 307 | 
            -
                      # TODO: do this with the perform chokepoint?
         | 
| 308 | 
            -
                      # But given the fact that fetching the response doesn't take place
         | 
| 309 | 
            -
                      # in that slot it's misleading anyway. Need to move all of this method
         | 
| 310 | 
            -
                      # into perform to be meaningful
         | 
| 311 | 
            -
                      server.request(:send_multiget, keys_for_server)
         | 
| 312 | 
            -
                    rescue DalliError, NetworkError => e
         | 
| 313 | 
            -
                      Dalli.logger.debug { e.inspect }
         | 
| 314 | 
            -
                      Dalli.logger.debug { "unable to get keys for server #{server.name}" }
         | 
| 315 | 
            -
                    end
         | 
| 316 | 
            -
                  end
         | 
| 317 | 
            -
                end
         | 
| 318 | 
            -
             | 
| 319 | 
            -
                def perform_multi_response_start(servers)
         | 
| 320 | 
            -
                  servers.each do |server|
         | 
| 321 | 
            -
                    next unless server.alive?
         | 
| 322 | 
            -
                    begin
         | 
| 323 | 
            -
                      server.multi_response_start
         | 
| 324 | 
            -
                    rescue DalliError, NetworkError => e
         | 
| 325 | 
            -
                      Dalli.logger.debug { e.inspect }
         | 
| 326 | 
            -
                      Dalli.logger.debug { "results from this server will be missing" }
         | 
| 327 | 
            -
                      servers.delete(server)
         | 
| 328 | 
            -
                    end
         | 
| 329 | 
            -
                  end
         | 
| 330 | 
            -
                  servers
         | 
| 331 | 
            -
                end
         | 
| 332 | 
            -
             | 
| 333 | 
            -
                ##
         | 
| 334 | 
            -
                # Normalizes the argument into an array of servers.
         | 
| 335 | 
            -
                # If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
         | 
| 336 | 
            -
                # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
         | 
| 337 | 
            -
                def normalize_servers(servers)
         | 
| 338 | 
            -
                  Array(servers).flat_map do |server|
         | 
| 339 | 
            -
                    if server.is_a? String
         | 
| 340 | 
            -
                      server.split(",")
         | 
| 341 | 
            -
                    else
         | 
| 342 | 
            -
                      server
         | 
| 343 | 
            -
                    end
         | 
| 344 | 
            -
                  end
         | 
| 345 | 
            -
                end
         | 
| 346 | 
            -
             | 
| 347 395 | 
             
                def ring
         | 
| 396 | 
            +
                  # TODO: This server initialization should probably be pushed down
         | 
| 397 | 
            +
                  # to the Ring
         | 
| 348 398 | 
             
                  @ring ||= Dalli::Ring.new(
         | 
| 349 399 | 
             
                    @servers.map do |s|
         | 
| 350 | 
            -
             | 
| 351 | 
            -
                      if s =~ %r{\Amemcached://}
         | 
| 352 | 
            -
                        uri = URI.parse(s)
         | 
| 353 | 
            -
                        server_options[:username] = uri.user
         | 
| 354 | 
            -
                        server_options[:password] = uri.password
         | 
| 355 | 
            -
                        s = "#{uri.host}:#{uri.port}"
         | 
| 356 | 
            -
                      end
         | 
| 357 | 
            -
                      Dalli::Server.new(s, @options.merge(server_options))
         | 
| 400 | 
            +
                      protocol_implementation.new(s, @options)
         | 
| 358 401 | 
             
                    end, @options
         | 
| 359 402 | 
             
                  )
         | 
| 360 403 | 
             
                end
         | 
| 361 404 |  | 
| 362 | 
            -
                 | 
| 363 | 
            -
             | 
| 364 | 
            -
                  return yield if block_given?
         | 
| 365 | 
            -
                  op, key, *args = *all_args
         | 
| 366 | 
            -
             | 
| 367 | 
            -
                  key = key.to_s
         | 
| 368 | 
            -
                  key = validate_key(key)
         | 
| 369 | 
            -
                  begin
         | 
| 370 | 
            -
                    server = ring.server_for_key(key)
         | 
| 371 | 
            -
                    ret = server.request(op, key, *args)
         | 
| 372 | 
            -
                    ret
         | 
| 373 | 
            -
                  rescue NetworkError => e
         | 
| 374 | 
            -
                    Dalli.logger.debug { e.inspect }
         | 
| 375 | 
            -
                    Dalli.logger.debug { "retrying request with new server" }
         | 
| 376 | 
            -
                    retry
         | 
| 377 | 
            -
                  end
         | 
| 405 | 
            +
                def protocol_implementation
         | 
| 406 | 
            +
                  @protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary)
         | 
| 378 407 | 
             
                end
         | 
| 379 408 |  | 
| 380 | 
            -
                 | 
| 381 | 
            -
             | 
| 382 | 
            -
             | 
| 383 | 
            -
             | 
| 384 | 
            -
             | 
| 385 | 
            -
             | 
| 386 | 
            -
             | 
| 387 | 
            -
             | 
| 388 | 
            -
             | 
| 389 | 
            -
                 | 
| 409 | 
            +
                ##
         | 
| 410 | 
            +
                # Chokepoint method for memcached methods with a key argument.
         | 
| 411 | 
            +
                # Validates the key, resolves the key to the appropriate server
         | 
| 412 | 
            +
                # instance, and invokes the memcached method on the appropriate
         | 
| 413 | 
            +
                # server.
         | 
| 414 | 
            +
                #
         | 
| 415 | 
            +
                # This method also forces retries on network errors - when
         | 
| 416 | 
            +
                # a particular memcached instance becomes unreachable, or the
         | 
| 417 | 
            +
                # operational times out.
         | 
| 418 | 
            +
                ##
         | 
| 419 | 
            +
                def perform(*all_args)
         | 
| 420 | 
            +
                  return yield if block_given?
         | 
| 390 421 |  | 
| 391 | 
            -
             | 
| 392 | 
            -
                  (ns = namespace) ? "#{ns}:#{key}" : key
         | 
| 393 | 
            -
                end
         | 
| 422 | 
            +
                  op, key, *args = all_args
         | 
| 394 423 |  | 
| 395 | 
            -
             | 
| 396 | 
            -
                   | 
| 397 | 
            -
                end
         | 
| 424 | 
            +
                  key = key.to_s
         | 
| 425 | 
            +
                  key = @key_manager.validate_key(key)
         | 
| 398 426 |  | 
| 399 | 
            -
             | 
| 400 | 
            -
                   | 
| 401 | 
            -
             | 
| 427 | 
            +
                  server = ring.server_for_key(key)
         | 
| 428 | 
            +
                  server.request(op, key, *args)
         | 
| 429 | 
            +
                rescue NetworkError => e
         | 
| 430 | 
            +
                  Dalli.logger.debug { e.inspect }
         | 
| 431 | 
            +
                  Dalli.logger.debug { 'retrying request with new server' }
         | 
| 432 | 
            +
                  retry
         | 
| 402 433 | 
             
                end
         | 
| 403 434 |  | 
| 404 435 | 
             
                def normalize_options(opts)
         | 
| 405 | 
            -
                  if opts[: | 
| 406 | 
            -
                    Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true.  Please update your configuration."
         | 
| 407 | 
            -
                    opts[:compress] = opts.delete(:compression)
         | 
| 408 | 
            -
                  end
         | 
| 409 | 
            -
                  begin
         | 
| 410 | 
            -
                    opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
         | 
| 411 | 
            -
                  rescue NoMethodError
         | 
| 412 | 
            -
                    raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
         | 
| 413 | 
            -
                  end
         | 
| 414 | 
            -
                  if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
         | 
| 415 | 
            -
                    raise ArgumentError, "The digest_class object must respond to the hexdigest method"
         | 
| 416 | 
            -
                  end
         | 
| 436 | 
            +
                  opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
         | 
| 417 437 | 
             
                  opts
         | 
| 438 | 
            +
                rescue NoMethodError
         | 
| 439 | 
            +
                  raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
         | 
| 418 440 | 
             
                end
         | 
| 419 441 |  | 
| 420 | 
            -
                 | 
| 421 | 
            -
             | 
| 422 | 
            -
                def get_multi_yielder(keys)
         | 
| 423 | 
            -
                  perform do
         | 
| 424 | 
            -
                    return {} if keys.empty?
         | 
| 425 | 
            -
                    ring.lock do
         | 
| 426 | 
            -
                      begin
         | 
| 427 | 
            -
                        groups = groups_for_keys(keys)
         | 
| 428 | 
            -
                        if unfound_keys = groups.delete(nil)
         | 
| 429 | 
            -
                          Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
         | 
| 430 | 
            -
                        end
         | 
| 431 | 
            -
                        make_multi_get_requests(groups)
         | 
| 432 | 
            -
             | 
| 433 | 
            -
                        servers = groups.keys
         | 
| 434 | 
            -
                        return if servers.empty?
         | 
| 435 | 
            -
                        servers = perform_multi_response_start(servers)
         | 
| 436 | 
            -
             | 
| 437 | 
            -
                        start = Time.now
         | 
| 438 | 
            -
                        while true
         | 
| 439 | 
            -
                          # remove any dead servers
         | 
| 440 | 
            -
                          servers.delete_if { |s| s.sock.nil? }
         | 
| 441 | 
            -
                          break if servers.empty?
         | 
| 442 | 
            -
             | 
| 443 | 
            -
                          # calculate remaining timeout
         | 
| 444 | 
            -
                          elapsed = Time.now - start
         | 
| 445 | 
            -
                          timeout = servers.first.options[:socket_timeout]
         | 
| 446 | 
            -
                          time_left = (elapsed > timeout) ? 0 : timeout - elapsed
         | 
| 447 | 
            -
             | 
| 448 | 
            -
                          sockets = servers.map(&:sock)
         | 
| 449 | 
            -
                          readable, _ = IO.select(sockets, nil, nil, time_left)
         | 
| 450 | 
            -
             | 
| 451 | 
            -
                          if readable.nil?
         | 
| 452 | 
            -
                            # no response within timeout; abort pending connections
         | 
| 453 | 
            -
                            servers.each do |server|
         | 
| 454 | 
            -
                              Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
         | 
| 455 | 
            -
                              server.multi_response_abort
         | 
| 456 | 
            -
                            end
         | 
| 457 | 
            -
                            break
         | 
| 458 | 
            -
             | 
| 459 | 
            -
                          else
         | 
| 460 | 
            -
                            readable.each do |sock|
         | 
| 461 | 
            -
                              server = sock.server
         | 
| 462 | 
            -
             | 
| 463 | 
            -
                              begin
         | 
| 464 | 
            -
                                server.multi_response_nonblock.each_pair do |key, value_list|
         | 
| 465 | 
            -
                                  yield key_without_namespace(key), value_list
         | 
| 466 | 
            -
                                end
         | 
| 467 | 
            -
             | 
| 468 | 
            -
                                if server.multi_response_completed?
         | 
| 469 | 
            -
                                  servers.delete(server)
         | 
| 470 | 
            -
                                end
         | 
| 471 | 
            -
                              rescue NetworkError
         | 
| 472 | 
            -
                                servers.delete(server)
         | 
| 473 | 
            -
                              end
         | 
| 474 | 
            -
                            end
         | 
| 475 | 
            -
                          end
         | 
| 476 | 
            -
                        end
         | 
| 477 | 
            -
                      end
         | 
| 478 | 
            -
                    end
         | 
| 479 | 
            -
                  end
         | 
| 442 | 
            +
                def pipelined_getter
         | 
| 443 | 
            +
                  PipelinedGetter.new(ring, @key_manager)
         | 
| 480 444 | 
             
                end
         | 
| 481 | 
            -
             | 
| 482 445 | 
             
              end
         | 
| 483 446 | 
             
            end
         |