dalli 3.0.4 → 3.1.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.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/Gemfile +11 -5
 - data/History.md +31 -0
 - data/README.md +25 -134
 - data/lib/dalli/cas/client.rb +2 -0
 - data/lib/dalli/client.rb +215 -323
 - data/lib/dalli/compressor.rb +13 -4
 - data/lib/dalli/key_manager.rb +113 -0
 - data/lib/dalli/options.rb +5 -5
 - data/lib/dalli/pipelined_getter.rb +177 -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 +200 -0
 - data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
 - data/lib/dalli/protocol/binary.rb +251 -561
 - 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 +22 -5
 - data/lib/dalli/protocol/value_marshaller.rb +59 -0
 - data/lib/dalli/protocol/value_serializer.rb +91 -0
 - data/lib/dalli/protocol.rb +2 -3
 - data/lib/dalli/ring.rb +95 -35
 - data/lib/dalli/server.rb +2 -2
 - data/lib/dalli/servers_arg_normalizer.rb +54 -0
 - data/lib/dalli/socket.rb +101 -55
 - data/lib/dalli/version.rb +3 -1
 - data/lib/dalli.rb +39 -14
 - data/lib/rack/session/dalli.rb +95 -76
 - metadata +80 -6
 
| 
         @@ -1,703 +1,393 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require  
     | 
| 
       4 
     | 
    
         
            -
            require  
     | 
| 
      
 3 
     | 
    
         
            +
            require 'forwardable'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'timeout'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative 'binary/request_formatter'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative 'binary/response_header'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative 'binary/response_processor'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative 'binary/sasl_authentication'
         
     | 
| 
       5 
11 
     | 
    
         | 
| 
       6 
12 
     | 
    
         
             
            module Dalli
         
     | 
| 
       7 
13 
     | 
    
         
             
              module Protocol
         
     | 
| 
      
 14 
     | 
    
         
            +
                ##
         
     | 
| 
      
 15 
     | 
    
         
            +
                # Access point for a single Memcached server, accessed via Memcached's binary
         
     | 
| 
      
 16 
     | 
    
         
            +
                # protocol.  Contains logic for managing connection state to the server (retries, etc),
         
     | 
| 
      
 17 
     | 
    
         
            +
                # formatting requests to the server, and unpacking responses.
         
     | 
| 
      
 18 
     | 
    
         
            +
                ##
         
     | 
| 
       8 
19 
     | 
    
         
             
                class Binary
         
     | 
| 
       9 
     | 
    
         
            -
                   
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                  attr_accessor :weight
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                   
     | 
| 
       14 
     | 
    
         
            -
                   
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                     
     | 
| 
       19 
     | 
    
         
            -
                     
     | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
                     
     | 
| 
       22 
     | 
    
         
            -
                     
     | 
| 
       23 
     | 
    
         
            -
                    # amount of time to sleep between retries when a failure occurs
         
     | 
| 
       24 
     | 
    
         
            -
                    socket_failure_delay: 0.1,
         
     | 
| 
       25 
     | 
    
         
            -
                    # max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
         
     | 
| 
       26 
     | 
    
         
            -
                    value_max_bytes: 1024 * 1024,
         
     | 
| 
       27 
     | 
    
         
            -
                    serializer: Marshal,
         
     | 
| 
       28 
     | 
    
         
            -
                    username: nil,
         
     | 
| 
       29 
     | 
    
         
            -
                    password: nil,
         
     | 
| 
       30 
     | 
    
         
            -
                    keepalive: true,
         
     | 
| 
       31 
     | 
    
         
            -
                    # max byte size for SO_SNDBUF
         
     | 
| 
       32 
     | 
    
         
            -
                    sndbuf: nil,
         
     | 
| 
       33 
     | 
    
         
            -
                    # max byte size for SO_RCVBUF
         
     | 
| 
       34 
     | 
    
         
            -
                    rcvbuf: nil
         
     | 
| 
       35 
     | 
    
         
            -
                  }
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                  def initialize(attribs, options = {})
         
     | 
| 
       38 
     | 
    
         
            -
                    @hostname, @port, @weight, @socket_type = ServerConfigParser.parse(attribs)
         
     | 
| 
       39 
     | 
    
         
            -
                    @fail_count = 0
         
     | 
| 
       40 
     | 
    
         
            -
                    @down_at = nil
         
     | 
| 
       41 
     | 
    
         
            -
                    @last_down_at = nil
         
     | 
| 
       42 
     | 
    
         
            -
                    @options = DEFAULTS.merge(options)
         
     | 
| 
       43 
     | 
    
         
            -
                    @value_compressor = ValueCompressor.new(@options)
         
     | 
| 
       44 
     | 
    
         
            -
                    @sock = nil
         
     | 
| 
       45 
     | 
    
         
            -
                    @msg = nil
         
     | 
| 
       46 
     | 
    
         
            -
                    @error = nil
         
     | 
| 
       47 
     | 
    
         
            -
                    @pid = nil
         
     | 
| 
       48 
     | 
    
         
            -
                    @inprogress = nil
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  def name
         
     | 
| 
       52 
     | 
    
         
            -
                    if socket_type == :unix
         
     | 
| 
       53 
     | 
    
         
            -
                      hostname
         
     | 
| 
       54 
     | 
    
         
            -
                    else
         
     | 
| 
       55 
     | 
    
         
            -
                      "#{hostname}:#{port}"
         
     | 
| 
       56 
     | 
    
         
            -
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  attr_accessor :weight, :options
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default?
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def_delegators :@connection_manager, :name, :sock, :hostname, :port, :close, :connected?, :socket_timeout,
         
     | 
| 
      
 26 
     | 
    
         
            +
                                 :socket_type, :up!, :down!, :write, :reconnect_down_server?, :raise_down_error
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def initialize(attribs, client_options = {})
         
     | 
| 
      
 29 
     | 
    
         
            +
                    hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @options = client_options.merge(user_creds)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @value_marshaller = ValueMarshaller.new(@options)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @response_processor = ResponseProcessor.new(@connection_manager, @value_marshaller)
         
     | 
| 
       57 
34 
     | 
    
         
             
                  end
         
     | 
| 
       58 
35 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                  # Chokepoint method for  
     | 
| 
       60 
     | 
    
         
            -
                  def request( 
     | 
| 
       61 
     | 
    
         
            -
                    verify_state
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 36 
     | 
    
         
            +
                  # Chokepoint method for error handling and ensuring liveness
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def request(opkey, *args)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    verify_state(opkey)
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
       63 
40 
     | 
    
         
             
                    begin
         
     | 
| 
       64 
     | 
    
         
            -
                      send( 
     | 
| 
       65 
     | 
    
         
            -
                    rescue Dalli::MarshalError =>  
     | 
| 
       66 
     | 
    
         
            -
                       
     | 
| 
       67 
     | 
    
         
            -
                      Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
         
     | 
| 
      
 41 
     | 
    
         
            +
                      send(opkey, *args)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    rescue Dalli::MarshalError => e
         
     | 
| 
      
 43 
     | 
    
         
            +
                      log_marshal_err(args.first, e)
         
     | 
| 
       68 
44 
     | 
    
         
             
                      raise
         
     | 
| 
       69 
     | 
    
         
            -
                    rescue Dalli::DalliError 
     | 
| 
      
 45 
     | 
    
         
            +
                    rescue Dalli::DalliError
         
     | 
| 
       70 
46 
     | 
    
         
             
                      raise
         
     | 
| 
       71 
     | 
    
         
            -
                    rescue =>  
     | 
| 
       72 
     | 
    
         
            -
                       
     | 
| 
       73 
     | 
    
         
            -
                      Dalli.logger.error ex.backtrace.join("\n\t")
         
     | 
| 
      
 47 
     | 
    
         
            +
                    rescue StandardError => e
         
     | 
| 
      
 48 
     | 
    
         
            +
                      log_unexpected_err(e)
         
     | 
| 
       74 
49 
     | 
    
         
             
                      down!
         
     | 
| 
       75 
50 
     | 
    
         
             
                    end
         
     | 
| 
       76 
51 
     | 
    
         
             
                  end
         
     | 
| 
       77 
52 
     | 
    
         | 
| 
      
 53 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # Boolean method used by clients of this class to determine if this
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # particular memcached instance is available for use.
         
     | 
| 
       78 
56 
     | 
    
         
             
                  def alive?
         
     | 
| 
       79 
     | 
    
         
            -
                     
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
                    if @last_down_at && @last_down_at + options[:down_retry_delay] >= Time.now
         
     | 
| 
       82 
     | 
    
         
            -
                      time = @last_down_at + options[:down_retry_delay] - Time.now
         
     | 
| 
       83 
     | 
    
         
            -
                      Dalli.logger.debug { "down_retry_delay not reached for #{name} (%.3f seconds left)" % time }
         
     | 
| 
       84 
     | 
    
         
            -
                      return false
         
     | 
| 
       85 
     | 
    
         
            -
                    end
         
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
                    connect
         
     | 
| 
       88 
     | 
    
         
            -
                    !!@sock
         
     | 
| 
      
 57 
     | 
    
         
            +
                    ensure_connected!
         
     | 
| 
       89 
58 
     | 
    
         
             
                  rescue Dalli::NetworkError
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # ensure_connected! raises a NetworkError if connection fails.  We
         
     | 
| 
      
 60 
     | 
    
         
            +
                    # want to capture that error and convert it to a boolean value here.
         
     | 
| 
       90 
61 
     | 
    
         
             
                    false
         
     | 
| 
       91 
62 
     | 
    
         
             
                  end
         
     | 
| 
       92 
63 
     | 
    
         | 
| 
       93 
     | 
    
         
            -
                  def  
     | 
| 
       94 
     | 
    
         
            -
                    return unless @sock
         
     | 
| 
       95 
     | 
    
         
            -
                    begin
         
     | 
| 
       96 
     | 
    
         
            -
                      @sock.close
         
     | 
| 
       97 
     | 
    
         
            -
                    rescue
         
     | 
| 
       98 
     | 
    
         
            -
                      nil
         
     | 
| 
       99 
     | 
    
         
            -
                    end
         
     | 
| 
       100 
     | 
    
         
            -
                    @sock = nil
         
     | 
| 
       101 
     | 
    
         
            -
                    @pid = nil
         
     | 
| 
       102 
     | 
    
         
            -
                    @inprogress = false
         
     | 
| 
       103 
     | 
    
         
            -
                  end
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                  def lock!
         
     | 
| 
       106 
     | 
    
         
            -
                  end
         
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
                  def unlock!
         
     | 
| 
       109 
     | 
    
         
            -
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  def lock!; end
         
     | 
| 
       110 
65 
     | 
    
         | 
| 
       111 
     | 
    
         
            -
                  def  
     | 
| 
       112 
     | 
    
         
            -
                    @options[:serializer]
         
     | 
| 
       113 
     | 
    
         
            -
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  def unlock!; end
         
     | 
| 
       114 
67 
     | 
    
         | 
| 
       115 
68 
     | 
    
         
             
                  # Start reading key/value pairs from this connection. This is usually called
         
     | 
| 
       116 
69 
     | 
    
         
             
                  # after a series of GETKQ commands. A NOOP is sent, and the server begins
         
     | 
| 
       117 
70 
     | 
    
         
             
                  # flushing responses for kv pairs that were found.
         
     | 
| 
       118 
71 
     | 
    
         
             
                  #
         
     | 
| 
       119 
72 
     | 
    
         
             
                  # Returns nothing.
         
     | 
| 
       120 
     | 
    
         
            -
                  def  
     | 
| 
       121 
     | 
    
         
            -
                    verify_state
         
     | 
| 
      
 73 
     | 
    
         
            +
                  def pipeline_response_setup
         
     | 
| 
      
 74 
     | 
    
         
            +
                    verify_state(:getkq)
         
     | 
| 
       122 
75 
     | 
    
         
             
                    write_noop
         
     | 
| 
       123 
     | 
    
         
            -
                     
     | 
| 
       124 
     | 
    
         
            -
                    @ 
     | 
| 
       125 
     | 
    
         
            -
                    @inprogress = true
         
     | 
| 
       126 
     | 
    
         
            -
                  end
         
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
     | 
    
         
            -
                  # Did the last call to #multi_response_start complete successfully?
         
     | 
| 
       129 
     | 
    
         
            -
                  def multi_response_completed?
         
     | 
| 
       130 
     | 
    
         
            -
                    @multi_buffer.nil?
         
     | 
| 
      
 76 
     | 
    
         
            +
                    response_buffer.reset
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @connection_manager.start_request!
         
     | 
| 
       131 
78 
     | 
    
         
             
                  end
         
     | 
| 
       132 
79 
     | 
    
         | 
| 
       133 
80 
     | 
    
         
             
                  # Attempt to receive and parse as many key/value pairs as possible
         
     | 
| 
       134 
     | 
    
         
            -
                  # from this server. After # 
     | 
| 
      
 81 
     | 
    
         
            +
                  # from this server. After #pipeline_response_setup, this should be invoked
         
     | 
| 
       135 
82 
     | 
    
         
             
                  # repeatedly whenever this server's socket is readable until
         
     | 
| 
       136 
     | 
    
         
            -
                  # # 
     | 
| 
      
 83 
     | 
    
         
            +
                  # #pipeline_complete?.
         
     | 
| 
       137 
84 
     | 
    
         
             
                  #
         
     | 
| 
       138 
85 
     | 
    
         
             
                  # Returns a Hash of kv pairs received.
         
     | 
| 
       139 
     | 
    
         
            -
                  def  
     | 
| 
       140 
     | 
    
         
            -
                     
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
                    @multi_buffer << @sock.read_available
         
     | 
| 
       143 
     | 
    
         
            -
                    buf = @multi_buffer
         
     | 
| 
       144 
     | 
    
         
            -
                    pos = @position
         
     | 
| 
      
 86 
     | 
    
         
            +
                  def pipeline_next_responses
         
     | 
| 
      
 87 
     | 
    
         
            +
                    reconnect_on_pipeline_complete!
         
     | 
| 
       145 
88 
     | 
    
         
             
                    values = {}
         
     | 
| 
       146 
89 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
                     
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
                       
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
                        pos = pos + 24 + body_length
         
     | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
                        begin
         
     | 
| 
       166 
     | 
    
         
            -
                          values[key] = [deserialize(value, flags), cas]
         
     | 
| 
       167 
     | 
    
         
            -
                        rescue DalliError
         
     | 
| 
       168 
     | 
    
         
            -
                        end
         
     | 
| 
       169 
     | 
    
         
            -
             
     | 
| 
       170 
     | 
    
         
            -
                      else
         
     | 
| 
       171 
     | 
    
         
            -
                        # not enough data yet, wait for more
         
     | 
| 
       172 
     | 
    
         
            -
                        break
         
     | 
| 
       173 
     | 
    
         
            -
                      end
         
     | 
| 
      
 90 
     | 
    
         
            +
                    response_buffer.read
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    resp_header, key, value = pipeline_response
         
     | 
| 
      
 93 
     | 
    
         
            +
                    # resp_header is not nil only if we have a full response to parse
         
     | 
| 
      
 94 
     | 
    
         
            +
                    # in the buffer
         
     | 
| 
      
 95 
     | 
    
         
            +
                    while resp_header
         
     | 
| 
      
 96 
     | 
    
         
            +
                      # If the status is ok and key is nil, then this is the response
         
     | 
| 
      
 97 
     | 
    
         
            +
                      # to the noop at the end of the pipeline
         
     | 
| 
      
 98 
     | 
    
         
            +
                      finish_pipeline && break if resp_header.ok? && key.nil?
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                      # If the status is ok and the key is not nil, then this is a
         
     | 
| 
      
 101 
     | 
    
         
            +
                      # getkq response with a value that we want to set in the response hash
         
     | 
| 
      
 102 
     | 
    
         
            +
                      values[key] = [value, resp_header.cas] unless key.nil?
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                      # Get the next response from the buffer
         
     | 
| 
      
 105 
     | 
    
         
            +
                      resp_header, key, value = pipeline_response
         
     | 
| 
       174 
106 
     | 
    
         
             
                    end
         
     | 
| 
       175 
     | 
    
         
            -
                    @position = pos
         
     | 
| 
       176 
107 
     | 
    
         | 
| 
       177 
108 
     | 
    
         
             
                    values
         
     | 
| 
       178 
109 
     | 
    
         
             
                  rescue SystemCallError, Timeout::Error, EOFError => e
         
     | 
| 
       179 
     | 
    
         
            -
                     
     | 
| 
      
 110 
     | 
    
         
            +
                    @connection_manager.error_on_request!(e)
         
     | 
| 
       180 
111 
     | 
    
         
             
                  end
         
     | 
| 
       181 
112 
     | 
    
         | 
| 
       182 
     | 
    
         
            -
                  # Abort  
     | 
| 
       183 
     | 
    
         
            -
                  # timeout. 
     | 
| 
       184 
     | 
    
         
            -
                  # swallowed.
         
     | 
| 
      
 113 
     | 
    
         
            +
                  # Abort current pipelined get. Generally used to signal an external
         
     | 
| 
      
 114 
     | 
    
         
            +
                  # timeout during pipelined get.  The underlying socket is
         
     | 
| 
      
 115 
     | 
    
         
            +
                  # disconnected, and the exception is swallowed.
         
     | 
| 
       185 
116 
     | 
    
         
             
                  #
         
     | 
| 
       186 
117 
     | 
    
         
             
                  # Returns nothing.
         
     | 
| 
       187 
     | 
    
         
            -
                  def  
     | 
| 
       188 
     | 
    
         
            -
                     
     | 
| 
       189 
     | 
    
         
            -
                    @ 
     | 
| 
       190 
     | 
    
         
            -
                     
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
      
 118 
     | 
    
         
            +
                  def pipeline_abort
         
     | 
| 
      
 119 
     | 
    
         
            +
                    response_buffer.clear
         
     | 
| 
      
 120 
     | 
    
         
            +
                    @connection_manager.abort_request!
         
     | 
| 
      
 121 
     | 
    
         
            +
                    return true unless connected?
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    # Closes the connection, which ensures that our connection
         
     | 
| 
      
 124 
     | 
    
         
            +
                    # is in a clean state for future requests
         
     | 
| 
      
 125 
     | 
    
         
            +
                    @connection_manager.error_on_request!('External timeout')
         
     | 
| 
       192 
126 
     | 
    
         
             
                  rescue NetworkError
         
     | 
| 
       193 
127 
     | 
    
         
             
                    true
         
     | 
| 
       194 
128 
     | 
    
         
             
                  end
         
     | 
| 
       195 
129 
     | 
    
         | 
| 
       196 
     | 
    
         
            -
                  #  
     | 
| 
      
 130 
     | 
    
         
            +
                  # Did the last call to #pipeline_response_setup complete successfully?
         
     | 
| 
      
 131 
     | 
    
         
            +
                  def pipeline_complete?
         
     | 
| 
      
 132 
     | 
    
         
            +
                    !response_buffer.in_progress?
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
       197 
134 
     | 
    
         | 
| 
       198 
     | 
    
         
            -
                   
     | 
| 
      
 135 
     | 
    
         
            +
                  def username
         
     | 
| 
      
 136 
     | 
    
         
            +
                    @options[:username] || ENV['MEMCACHE_USERNAME']
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
       199 
138 
     | 
    
         | 
| 
       200 
     | 
    
         
            -
                  def  
     | 
| 
       201 
     | 
    
         
            -
                     
     | 
| 
       202 
     | 
    
         
            -
                    if @pid && @pid != Process.pid
         
     | 
| 
       203 
     | 
    
         
            -
                      message = "Fork detected, re-connecting child process..."
         
     | 
| 
       204 
     | 
    
         
            -
                      Dalli.logger.info { message }
         
     | 
| 
       205 
     | 
    
         
            -
                      reconnect! message
         
     | 
| 
       206 
     | 
    
         
            -
                    end
         
     | 
| 
      
 139 
     | 
    
         
            +
                  def password
         
     | 
| 
      
 140 
     | 
    
         
            +
                    @options[:password] || ENV['MEMCACHE_PASSWORD']
         
     | 
| 
       207 
141 
     | 
    
         
             
                  end
         
     | 
| 
       208 
142 
     | 
    
         | 
| 
       209 
     | 
    
         
            -
                  def  
     | 
| 
       210 
     | 
    
         
            -
                     
     | 
| 
       211 
     | 
    
         
            -
                    sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
         
     | 
| 
       212 
     | 
    
         
            -
                    raise Dalli::NetworkError, message
         
     | 
| 
      
 143 
     | 
    
         
            +
                  def require_auth?
         
     | 
| 
      
 144 
     | 
    
         
            +
                    !username.nil?
         
     | 
| 
       213 
145 
     | 
    
         
             
                  end
         
     | 
| 
       214 
146 
     | 
    
         | 
| 
       215 
     | 
    
         
            -
                   
     | 
| 
       216 
     | 
    
         
            -
                    message = "#{name} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
         
     | 
| 
       217 
     | 
    
         
            -
                    Dalli.logger.warn { message }
         
     | 
| 
      
 147 
     | 
    
         
            +
                  # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
         
     | 
| 
       218 
148 
     | 
    
         | 
| 
       219 
     | 
    
         
            -
             
     | 
| 
       220 
     | 
    
         
            -
                    if @fail_count >= options[:socket_max_failures]
         
     | 
| 
       221 
     | 
    
         
            -
                      down!
         
     | 
| 
       222 
     | 
    
         
            -
                    else
         
     | 
| 
       223 
     | 
    
         
            -
                      reconnect! "Socket operation failed, retrying..."
         
     | 
| 
       224 
     | 
    
         
            -
                    end
         
     | 
| 
       225 
     | 
    
         
            -
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                  private
         
     | 
| 
       226 
150 
     | 
    
         | 
| 
       227 
     | 
    
         
            -
                   
     | 
| 
       228 
     | 
    
         
            -
             
     | 
| 
      
 151 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # Checks to see if we can execute the specified operation.  Checks
         
     | 
| 
      
 153 
     | 
    
         
            +
                  # whether the connection is in use, and whether the command is allowed
         
     | 
| 
      
 154 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 155 
     | 
    
         
            +
                  def verify_state(opkey)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    @connection_manager.confirm_ready!
         
     | 
| 
      
 157 
     | 
    
         
            +
                    verify_allowed_quiet!(opkey) if quiet?
         
     | 
| 
       229 
158 
     | 
    
         | 
| 
       230 
     | 
    
         
            -
                     
     | 
| 
      
 159 
     | 
    
         
            +
                    # The ensure_connected call has the side effect of connecting the
         
     | 
| 
      
 160 
     | 
    
         
            +
                    # underlying socket if it is not connected, or there's been a disconnect
         
     | 
| 
      
 161 
     | 
    
         
            +
                    # because of timeout or other error.  Method raises an error
         
     | 
| 
      
 162 
     | 
    
         
            +
                    # if it can't connect
         
     | 
| 
      
 163 
     | 
    
         
            +
                    raise_down_error unless ensure_connected!
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
       231 
165 
     | 
    
         | 
| 
       232 
     | 
    
         
            -
             
     | 
| 
       233 
     | 
    
         
            -
             
     | 
| 
       234 
     | 
    
         
            -
             
     | 
| 
       235 
     | 
    
         
            -
             
     | 
| 
       236 
     | 
    
         
            -
             
     | 
| 
       237 
     | 
    
         
            -
             
     | 
| 
       238 
     | 
    
         
            -
             
     | 
| 
      
 166 
     | 
    
         
            +
                  # The socket connection to the underlying server is initialized as a side
         
     | 
| 
      
 167 
     | 
    
         
            +
                  # effect of this call.  In fact, this is the ONLY place where that
         
     | 
| 
      
 168 
     | 
    
         
            +
                  # socket connection is initialized.
         
     | 
| 
      
 169 
     | 
    
         
            +
                  #
         
     | 
| 
      
 170 
     | 
    
         
            +
                  # Both this method and connect need to be in this class so we can do auth
         
     | 
| 
      
 171 
     | 
    
         
            +
                  # as required
         
     | 
| 
      
 172 
     | 
    
         
            +
                  #
         
     | 
| 
      
 173 
     | 
    
         
            +
                  # Since this is invoked exclusively in verify_state!, we don't need to worry about
         
     | 
| 
      
 174 
     | 
    
         
            +
                  # thread safety.  Using it elsewhere may require revisiting that assumption.
         
     | 
| 
      
 175 
     | 
    
         
            +
                  def ensure_connected!
         
     | 
| 
      
 176 
     | 
    
         
            +
                    return true if connected?
         
     | 
| 
      
 177 
     | 
    
         
            +
                    return false unless reconnect_down_server?
         
     | 
| 
       239 
178 
     | 
    
         | 
| 
       240 
     | 
    
         
            -
                     
     | 
| 
       241 
     | 
    
         
            -
                     
     | 
| 
       242 
     | 
    
         
            -
                    raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
         
     | 
| 
      
 179 
     | 
    
         
            +
                    connect # This call needs to be in this class so we can do auth
         
     | 
| 
      
 180 
     | 
    
         
            +
                    connected?
         
     | 
| 
       243 
181 
     | 
    
         
             
                  end
         
     | 
| 
       244 
182 
     | 
    
         | 
| 
       245 
     | 
    
         
            -
                   
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
     | 
    
         
            -
             
     | 
| 
       248 
     | 
    
         
            -
                      Dalli.logger.warn { "#{name} is back (downtime was %.3f seconds)" % time }
         
     | 
| 
       249 
     | 
    
         
            -
                    end
         
     | 
| 
      
 183 
     | 
    
         
            +
                  ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
         
     | 
| 
      
 184 
     | 
    
         
            +
                  def verify_allowed_quiet!(opkey)
         
     | 
| 
      
 185 
     | 
    
         
            +
                    return if ALLOWED_QUIET_OPS.include?(opkey)
         
     | 
| 
       250 
186 
     | 
    
         | 
| 
       251 
     | 
    
         
            -
                     
     | 
| 
       252 
     | 
    
         
            -
                    @down_at = nil
         
     | 
| 
       253 
     | 
    
         
            -
                    @last_down_at = nil
         
     | 
| 
       254 
     | 
    
         
            -
                    @msg = nil
         
     | 
| 
       255 
     | 
    
         
            -
                    @error = nil
         
     | 
| 
      
 187 
     | 
    
         
            +
                    raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
         
     | 
| 
       256 
188 
     | 
    
         
             
                  end
         
     | 
| 
       257 
189 
     | 
    
         | 
| 
       258 
     | 
    
         
            -
                  def  
     | 
| 
       259 
     | 
    
         
            -
                    Thread.current[ 
     | 
| 
      
 190 
     | 
    
         
            +
                  def quiet?
         
     | 
| 
      
 191 
     | 
    
         
            +
                    Thread.current[::Dalli::QUIET]
         
     | 
| 
       260 
192 
     | 
    
         
             
                  end
         
     | 
| 
       261 
193 
     | 
    
         | 
| 
      
 194 
     | 
    
         
            +
                  def cache_nils?(opts)
         
     | 
| 
      
 195 
     | 
    
         
            +
                    return false unless opts.is_a?(Hash)
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                    opts[:cache_nils] ? true : false
         
     | 
| 
      
 198 
     | 
    
         
            +
                  end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                  # Retrieval Commands
         
     | 
| 
       262 
201 
     | 
    
         
             
                  def get(key, options = nil)
         
     | 
| 
       263 
     | 
    
         
            -
                    req =  
     | 
| 
      
 202 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: :get, key: key)
         
     | 
| 
       264 
203 
     | 
    
         
             
                    write(req)
         
     | 
| 
       265 
     | 
    
         
            -
                    generic_response(true,  
     | 
| 
      
 204 
     | 
    
         
            +
                    @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
         
     | 
| 
       266 
205 
     | 
    
         
             
                  end
         
     | 
| 
       267 
206 
     | 
    
         | 
| 
       268 
     | 
    
         
            -
                  def  
     | 
| 
       269 
     | 
    
         
            -
                     
     | 
| 
       270 
     | 
    
         
            -
                     
     | 
| 
       271 
     | 
    
         
            -
                      req << [REQUEST, OPCODES[:getkq], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:getkq])
         
     | 
| 
       272 
     | 
    
         
            -
                    end
         
     | 
| 
       273 
     | 
    
         
            -
                    # Could send noop here instead of in multi_response_start
         
     | 
| 
      
 207 
     | 
    
         
            +
                  def gat(key, ttl, options = nil)
         
     | 
| 
      
 208 
     | 
    
         
            +
                    ttl = TtlSanitizer.sanitize(ttl)
         
     | 
| 
      
 209 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
         
     | 
| 
       274 
210 
     | 
    
         
             
                    write(req)
         
     | 
| 
      
 211 
     | 
    
         
            +
                    @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
         
     | 
| 
       275 
212 
     | 
    
         
             
                  end
         
     | 
| 
       276 
213 
     | 
    
         | 
| 
       277 
     | 
    
         
            -
                  def  
     | 
| 
       278 
     | 
    
         
            -
                    (value, flags) = serialize(key, value, options)
         
     | 
| 
      
 214 
     | 
    
         
            +
                  def touch(key, ttl)
         
     | 
| 
       279 
215 
     | 
    
         
             
                    ttl = TtlSanitizer.sanitize(ttl)
         
     | 
| 
      
 216 
     | 
    
         
            +
                    write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
         
     | 
| 
      
 217 
     | 
    
         
            +
                    @response_processor.generic_response
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
       280 
219 
     | 
    
         | 
| 
       281 
     | 
    
         
            -
             
     | 
| 
       282 
     | 
    
         
            -
             
     | 
| 
       283 
     | 
    
         
            -
             
     | 
| 
      
 220 
     | 
    
         
            +
                  # TODO: This is confusing, as there's a cas command in memcached
         
     | 
| 
      
 221 
     | 
    
         
            +
                  # and this isn't it.  Maybe rename?  Maybe eliminate?
         
     | 
| 
      
 222 
     | 
    
         
            +
                  def cas(key)
         
     | 
| 
      
 223 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: :get, key: key)
         
     | 
| 
       284 
224 
     | 
    
         
             
                    write(req)
         
     | 
| 
       285 
     | 
    
         
            -
                     
     | 
| 
      
 225 
     | 
    
         
            +
                    @response_processor.data_cas_response
         
     | 
| 
       286 
226 
     | 
    
         
             
                  end
         
     | 
| 
       287 
227 
     | 
    
         | 
| 
       288 
     | 
    
         
            -
                   
     | 
| 
       289 
     | 
    
         
            -
             
     | 
| 
       290 
     | 
    
         
            -
                     
     | 
| 
       291 
     | 
    
         
            -
             
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
      
 228 
     | 
    
         
            +
                  # Storage Commands
         
     | 
| 
      
 229 
     | 
    
         
            +
                  def set(key, value, ttl, cas, options)
         
     | 
| 
      
 230 
     | 
    
         
            +
                    opkey = quiet? ? :setq : :set
         
     | 
| 
      
 231 
     | 
    
         
            +
                    storage_req(opkey, key, value, ttl, cas, options)
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
       293 
233 
     | 
    
         | 
| 
       294 
     | 
    
         
            -
             
     | 
| 
       295 
     | 
    
         
            -
                     
     | 
| 
       296 
     | 
    
         
            -
                     
     | 
| 
      
 234 
     | 
    
         
            +
                  def add(key, value, ttl, options)
         
     | 
| 
      
 235 
     | 
    
         
            +
                    opkey = quiet? ? :addq : :add
         
     | 
| 
      
 236 
     | 
    
         
            +
                    storage_req(opkey, key, value, ttl, 0, options)
         
     | 
| 
       297 
237 
     | 
    
         
             
                  end
         
     | 
| 
       298 
238 
     | 
    
         | 
| 
       299 
239 
     | 
    
         
             
                  def replace(key, value, ttl, cas, options)
         
     | 
| 
       300 
     | 
    
         
            -
                     
     | 
| 
       301 
     | 
    
         
            -
                    ttl  
     | 
| 
      
 240 
     | 
    
         
            +
                    opkey = quiet? ? :replaceq : :replace
         
     | 
| 
      
 241 
     | 
    
         
            +
                    storage_req(opkey, key, value, ttl, cas, options)
         
     | 
| 
      
 242 
     | 
    
         
            +
                  end
         
     | 
| 
       302 
243 
     | 
    
         | 
| 
       303 
     | 
    
         
            -
             
     | 
| 
      
 244 
     | 
    
         
            +
                  # rubocop:disable Metrics/ParameterLists
         
     | 
| 
      
 245 
     | 
    
         
            +
                  def storage_req(opkey, key, value, ttl, cas, options)
         
     | 
| 
      
 246 
     | 
    
         
            +
                    (value, bitflags) = @value_marshaller.store(key, value, options)
         
     | 
| 
      
 247 
     | 
    
         
            +
                    ttl = TtlSanitizer.sanitize(ttl)
         
     | 
| 
       304 
248 
     | 
    
         | 
| 
       305 
     | 
    
         
            -
                    req =  
     | 
| 
      
 249 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: opkey, key: key,
         
     | 
| 
      
 250 
     | 
    
         
            +
                                                            value: value, bitflags: bitflags,
         
     | 
| 
      
 251 
     | 
    
         
            +
                                                            ttl: ttl, cas: cas)
         
     | 
| 
       306 
252 
     | 
    
         
             
                    write(req)
         
     | 
| 
       307 
     | 
    
         
            -
                     
     | 
| 
      
 253 
     | 
    
         
            +
                    @response_processor.storage_response unless quiet?
         
     | 
| 
       308 
254 
     | 
    
         
             
                  end
         
     | 
| 
      
 255 
     | 
    
         
            +
                  # rubocop:enable Metrics/ParameterLists
         
     | 
| 
       309 
256 
     | 
    
         | 
| 
       310 
     | 
    
         
            -
                  def  
     | 
| 
       311 
     | 
    
         
            -
                     
     | 
| 
       312 
     | 
    
         
            -
                     
     | 
| 
       313 
     | 
    
         
            -
                    generic_response unless multi?
         
     | 
| 
      
 257 
     | 
    
         
            +
                  def append(key, value)
         
     | 
| 
      
 258 
     | 
    
         
            +
                    opkey = quiet? ? :appendq : :append
         
     | 
| 
      
 259 
     | 
    
         
            +
                    write_append_prepend opkey, key, value
         
     | 
| 
       314 
260 
     | 
    
         
             
                  end
         
     | 
| 
       315 
261 
     | 
    
         | 
| 
       316 
     | 
    
         
            -
                  def  
     | 
| 
       317 
     | 
    
         
            -
                     
     | 
| 
       318 
     | 
    
         
            -
                     
     | 
| 
       319 
     | 
    
         
            -
                    generic_response
         
     | 
| 
      
 262 
     | 
    
         
            +
                  def prepend(key, value)
         
     | 
| 
      
 263 
     | 
    
         
            +
                    opkey = quiet? ? :prependq : :prepend
         
     | 
| 
      
 264 
     | 
    
         
            +
                    write_append_prepend opkey, key, value
         
     | 
| 
       320 
265 
     | 
    
         
             
                  end
         
     | 
| 
       321 
266 
     | 
    
         | 
| 
       322 
     | 
    
         
            -
                  def  
     | 
| 
       323 
     | 
    
         
            -
                     
     | 
| 
       324 
     | 
    
         
            -
                     
     | 
| 
       325 
     | 
    
         
            -
                    (h, l) = split(count)
         
     | 
| 
       326 
     | 
    
         
            -
                    (dh, dl) = split(default)
         
     | 
| 
       327 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[opcode], key.bytesize, 20, 0, 0, key.bytesize + 20, 0, 0, h, l, dh, dl, expiry, key].pack(FORMAT[opcode])
         
     | 
| 
       328 
     | 
    
         
            -
                    write(req)
         
     | 
| 
       329 
     | 
    
         
            -
                    body = generic_response
         
     | 
| 
       330 
     | 
    
         
            -
                    body ? body.unpack1("Q>") : body
         
     | 
| 
      
 267 
     | 
    
         
            +
                  def write_append_prepend(opkey, key, value)
         
     | 
| 
      
 268 
     | 
    
         
            +
                    write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
         
     | 
| 
      
 269 
     | 
    
         
            +
                    @response_processor.no_body_response unless quiet?
         
     | 
| 
       331 
270 
     | 
    
         
             
                  end
         
     | 
| 
       332 
271 
     | 
    
         | 
| 
       333 
     | 
    
         
            -
                   
     | 
| 
       334 
     | 
    
         
            -
             
     | 
| 
      
 272 
     | 
    
         
            +
                  # Delete Commands
         
     | 
| 
      
 273 
     | 
    
         
            +
                  def delete(key, cas)
         
     | 
| 
      
 274 
     | 
    
         
            +
                    opkey = quiet? ? :deleteq : :delete
         
     | 
| 
      
 275 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
         
     | 
| 
      
 276 
     | 
    
         
            +
                    write(req)
         
     | 
| 
      
 277 
     | 
    
         
            +
                    @response_processor.no_body_response unless quiet?
         
     | 
| 
       335 
278 
     | 
    
         
             
                  end
         
     | 
| 
       336 
279 
     | 
    
         | 
| 
       337 
     | 
    
         
            -
                   
     | 
| 
       338 
     | 
    
         
            -
             
     | 
| 
      
 280 
     | 
    
         
            +
                  # Arithmetic Commands
         
     | 
| 
      
 281 
     | 
    
         
            +
                  def decr(key, count, ttl, initial)
         
     | 
| 
      
 282 
     | 
    
         
            +
                    opkey = quiet? ? :decrq : :decr
         
     | 
| 
      
 283 
     | 
    
         
            +
                    decr_incr opkey, key, count, ttl, initial
         
     | 
| 
       339 
284 
     | 
    
         
             
                  end
         
     | 
| 
       340 
285 
     | 
    
         | 
| 
       341 
     | 
    
         
            -
                  def  
     | 
| 
       342 
     | 
    
         
            -
                     
     | 
| 
      
 286 
     | 
    
         
            +
                  def incr(key, count, ttl, initial)
         
     | 
| 
      
 287 
     | 
    
         
            +
                    opkey = quiet? ? :incrq : :incr
         
     | 
| 
      
 288 
     | 
    
         
            +
                    decr_incr opkey, key, count, ttl, initial
         
     | 
| 
       343 
289 
     | 
    
         
             
                  end
         
     | 
| 
       344 
290 
     | 
    
         | 
| 
       345 
     | 
    
         
            -
                   
     | 
| 
       346 
     | 
    
         
            -
             
     | 
| 
       347 
     | 
    
         
            -
             
     | 
| 
      
 291 
     | 
    
         
            +
                  # This allows us to special case a nil initial value, and
         
     | 
| 
      
 292 
     | 
    
         
            +
                  # handle it differently than a zero.  This special value
         
     | 
| 
      
 293 
     | 
    
         
            +
                  # for expiry causes memcached to return a not found
         
     | 
| 
      
 294 
     | 
    
         
            +
                  # if the key doesn't already exist, rather than
         
     | 
| 
      
 295 
     | 
    
         
            +
                  # setting the initial value
         
     | 
| 
      
 296 
     | 
    
         
            +
                  NOT_FOUND_EXPIRY = 0xFFFFFFFF
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                  def decr_incr(opkey, key, count, ttl, initial)
         
     | 
| 
      
 299 
     | 
    
         
            +
                    expiry = initial ? TtlSanitizer.sanitize(ttl) : NOT_FOUND_EXPIRY
         
     | 
| 
      
 300 
     | 
    
         
            +
                    initial ||= 0
         
     | 
| 
      
 301 
     | 
    
         
            +
                    write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
         
     | 
| 
      
 302 
     | 
    
         
            +
                                                             count: count, initial: initial, expiry: expiry))
         
     | 
| 
      
 303 
     | 
    
         
            +
                    @response_processor.decr_incr_response unless quiet?
         
     | 
| 
       348 
304 
     | 
    
         
             
                  end
         
     | 
| 
       349 
305 
     | 
    
         | 
| 
       350 
     | 
    
         
            -
                   
     | 
| 
       351 
     | 
    
         
            -
             
     | 
| 
       352 
     | 
    
         
            -
                     
     | 
| 
      
 306 
     | 
    
         
            +
                  # Other Commands
         
     | 
| 
      
 307 
     | 
    
         
            +
                  def flush(ttl = 0)
         
     | 
| 
      
 308 
     | 
    
         
            +
                    opkey = quiet? ? :flushq : :flush
         
     | 
| 
      
 309 
     | 
    
         
            +
                    write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
         
     | 
| 
      
 310 
     | 
    
         
            +
                    @response_processor.no_body_response unless quiet?
         
     | 
| 
       353 
311 
     | 
    
         
             
                  end
         
     | 
| 
       354 
312 
     | 
    
         | 
| 
       355 
313 
     | 
    
         
             
                  # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
         
     | 
| 
       356 
314 
     | 
    
         
             
                  # We need to read all the responses at once.
         
     | 
| 
       357 
315 
     | 
    
         
             
                  def noop
         
     | 
| 
       358 
316 
     | 
    
         
             
                    write_noop
         
     | 
| 
       359 
     | 
    
         
            -
                     
     | 
| 
      
 317 
     | 
    
         
            +
                    @response_processor.multi_with_keys_response
         
     | 
| 
       360 
318 
     | 
    
         
             
                  end
         
     | 
| 
       361 
319 
     | 
    
         | 
| 
       362 
     | 
    
         
            -
                  def  
     | 
| 
       363 
     | 
    
         
            -
                     
     | 
| 
       364 
     | 
    
         
            -
                  end
         
     | 
| 
       365 
     | 
    
         
            -
             
     | 
| 
       366 
     | 
    
         
            -
                  def prepend(key, value)
         
     | 
| 
       367 
     | 
    
         
            -
                    write_append_prepend :prepend, key, value
         
     | 
| 
       368 
     | 
    
         
            -
                  end
         
     | 
| 
       369 
     | 
    
         
            -
             
     | 
| 
       370 
     | 
    
         
            -
                  def stats(info = "")
         
     | 
| 
       371 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[:stat], info.bytesize, 0, 0, 0, info.bytesize, 0, 0, info].pack(FORMAT[:stat])
         
     | 
| 
      
 320 
     | 
    
         
            +
                  def stats(info = '')
         
     | 
| 
      
 321 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: :stat, key: info)
         
     | 
| 
       372 
322 
     | 
    
         
             
                    write(req)
         
     | 
| 
       373 
     | 
    
         
            -
                     
     | 
| 
      
 323 
     | 
    
         
            +
                    @response_processor.multi_with_keys_response
         
     | 
| 
       374 
324 
     | 
    
         
             
                  end
         
     | 
| 
       375 
325 
     | 
    
         | 
| 
       376 
326 
     | 
    
         
             
                  def reset_stats
         
     | 
| 
       377 
     | 
    
         
            -
                     
     | 
| 
       378 
     | 
    
         
            -
             
     | 
| 
       379 
     | 
    
         
            -
             
     | 
| 
       380 
     | 
    
         
            -
                  def cas(key)
         
     | 
| 
       381 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
         
     | 
| 
       382 
     | 
    
         
            -
                    write(req)
         
     | 
| 
       383 
     | 
    
         
            -
                    data_cas_response
         
     | 
| 
      
 327 
     | 
    
         
            +
                    write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
         
     | 
| 
      
 328 
     | 
    
         
            +
                    @response_processor.generic_response
         
     | 
| 
       384 
329 
     | 
    
         
             
                  end
         
     | 
| 
       385 
330 
     | 
    
         | 
| 
       386 
331 
     | 
    
         
             
                  def version
         
     | 
| 
       387 
     | 
    
         
            -
                     
     | 
| 
      
 332 
     | 
    
         
            +
                    write(RequestFormatter.standard_request(opkey: :version))
         
     | 
| 
      
 333 
     | 
    
         
            +
                    @response_processor.generic_response
         
     | 
| 
       388 
334 
     | 
    
         
             
                  end
         
     | 
| 
       389 
335 
     | 
    
         | 
| 
       390 
     | 
    
         
            -
                  def  
     | 
| 
       391 
     | 
    
         
            -
                     
     | 
| 
       392 
     | 
    
         
            -
                    write_generic [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
         
     | 
| 
       393 
     | 
    
         
            -
                  end
         
     | 
| 
       394 
     | 
    
         
            -
             
     | 
| 
       395 
     | 
    
         
            -
                  def gat(key, ttl, options = nil)
         
     | 
| 
       396 
     | 
    
         
            -
                    ttl = TtlSanitizer.sanitize(ttl)
         
     | 
| 
       397 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[:gat], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:gat])
         
     | 
| 
      
 336 
     | 
    
         
            +
                  def write_noop
         
     | 
| 
      
 337 
     | 
    
         
            +
                    req = RequestFormatter.standard_request(opkey: :noop)
         
     | 
| 
       398 
338 
     | 
    
         
             
                    write(req)
         
     | 
| 
       399 
     | 
    
         
            -
                    generic_response(true, !!(options && options.is_a?(Hash) && options[:cache_nils]))
         
     | 
| 
       400 
     | 
    
         
            -
                  end
         
     | 
| 
       401 
     | 
    
         
            -
             
     | 
| 
       402 
     | 
    
         
            -
                  # https://www.hjp.at/zettel/m/memcached_flags.rxml
         
     | 
| 
       403 
     | 
    
         
            -
                  # Looks like most clients use bit 0 to indicate native language serialization
         
     | 
| 
       404 
     | 
    
         
            -
                  FLAG_SERIALIZED = 0x1
         
     | 
| 
       405 
     | 
    
         
            -
             
     | 
| 
       406 
     | 
    
         
            -
                  def serialize(key, value, options = nil)
         
     | 
| 
       407 
     | 
    
         
            -
                    marshalled = false
         
     | 
| 
       408 
     | 
    
         
            -
                    value = if options && options[:raw]
         
     | 
| 
       409 
     | 
    
         
            -
                      value.to_s
         
     | 
| 
       410 
     | 
    
         
            -
                    else
         
     | 
| 
       411 
     | 
    
         
            -
                      marshalled = true
         
     | 
| 
       412 
     | 
    
         
            -
                      begin
         
     | 
| 
       413 
     | 
    
         
            -
                        serializer.dump(value)
         
     | 
| 
       414 
     | 
    
         
            -
                      rescue Timeout::Error => e
         
     | 
| 
       415 
     | 
    
         
            -
                        raise e
         
     | 
| 
       416 
     | 
    
         
            -
                      rescue => ex
         
     | 
| 
       417 
     | 
    
         
            -
                        # Marshalling can throw several different types of generic Ruby exceptions.
         
     | 
| 
       418 
     | 
    
         
            -
                        # Convert to a specific exception so we can special case it higher up the stack.
         
     | 
| 
       419 
     | 
    
         
            -
                        exc = Dalli::MarshalError.new(ex.message)
         
     | 
| 
       420 
     | 
    
         
            -
                        exc.set_backtrace ex.backtrace
         
     | 
| 
       421 
     | 
    
         
            -
                        raise exc
         
     | 
| 
       422 
     | 
    
         
            -
                      end
         
     | 
| 
       423 
     | 
    
         
            -
                    end
         
     | 
| 
       424 
     | 
    
         
            -
             
     | 
| 
       425 
     | 
    
         
            -
                    bitflags = 0
         
     | 
| 
       426 
     | 
    
         
            -
                    value, bitflags = @value_compressor.store(value, options, bitflags)
         
     | 
| 
       427 
     | 
    
         
            -
             
     | 
| 
       428 
     | 
    
         
            -
                    bitflags |= FLAG_SERIALIZED if marshalled
         
     | 
| 
       429 
     | 
    
         
            -
                    [value, bitflags]
         
     | 
| 
       430 
     | 
    
         
            -
                  end
         
     | 
| 
       431 
     | 
    
         
            -
             
     | 
| 
       432 
     | 
    
         
            -
                  def deserialize(value, flags)
         
     | 
| 
       433 
     | 
    
         
            -
                    value = @value_compressor.retrieve(value, flags)
         
     | 
| 
       434 
     | 
    
         
            -
                    value = serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
         
     | 
| 
       435 
     | 
    
         
            -
                    value
         
     | 
| 
       436 
     | 
    
         
            -
                  rescue TypeError
         
     | 
| 
       437 
     | 
    
         
            -
                    raise unless /needs to have method `_load'|exception class\/object expected|instance of IO needed|incompatible marshal file format/.match?($!.message)
         
     | 
| 
       438 
     | 
    
         
            -
                    raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
         
     | 
| 
       439 
     | 
    
         
            -
                  rescue ArgumentError
         
     | 
| 
       440 
     | 
    
         
            -
                    raise unless /undefined class|marshal data too short/.match?($!.message)
         
     | 
| 
       441 
     | 
    
         
            -
                    raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
         
     | 
| 
       442 
     | 
    
         
            -
                  rescue NameError
         
     | 
| 
       443 
     | 
    
         
            -
                    raise unless /uninitialized constant/.match?($!.message)
         
     | 
| 
       444 
     | 
    
         
            -
                    raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
         
     | 
| 
       445 
     | 
    
         
            -
                  end
         
     | 
| 
       446 
     | 
    
         
            -
             
     | 
| 
       447 
     | 
    
         
            -
                  def data_cas_response
         
     | 
| 
       448 
     | 
    
         
            -
                    (extras, _, status, count, _, cas) = read_header.unpack(CAS_HEADER)
         
     | 
| 
       449 
     | 
    
         
            -
                    data = read(count) if count > 0
         
     | 
| 
       450 
     | 
    
         
            -
                    if status == 1
         
     | 
| 
       451 
     | 
    
         
            -
                      nil
         
     | 
| 
       452 
     | 
    
         
            -
                    elsif status != 0
         
     | 
| 
       453 
     | 
    
         
            -
                      raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
         
     | 
| 
       454 
     | 
    
         
            -
                    elsif data
         
     | 
| 
       455 
     | 
    
         
            -
                      flags = data[0...extras].unpack1("N")
         
     | 
| 
       456 
     | 
    
         
            -
                      value = data[extras..-1]
         
     | 
| 
       457 
     | 
    
         
            -
                      data = deserialize(value, flags)
         
     | 
| 
       458 
     | 
    
         
            -
                    end
         
     | 
| 
       459 
     | 
    
         
            -
                    [data, cas]
         
     | 
| 
       460 
     | 
    
         
            -
                  end
         
     | 
| 
       461 
     | 
    
         
            -
             
     | 
| 
       462 
     | 
    
         
            -
                  CAS_HEADER = "@4CCnNNQ"
         
     | 
| 
       463 
     | 
    
         
            -
                  NORMAL_HEADER = "@4CCnN"
         
     | 
| 
       464 
     | 
    
         
            -
                  KV_HEADER = "@2n@6nN@16Q"
         
     | 
| 
       465 
     | 
    
         
            -
             
     | 
| 
       466 
     | 
    
         
            -
                  def guard_max_value(key, value)
         
     | 
| 
       467 
     | 
    
         
            -
                    return if value.bytesize <= @options[:value_max_bytes]
         
     | 
| 
       468 
     | 
    
         
            -
             
     | 
| 
       469 
     | 
    
         
            -
                    message = "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
         
     | 
| 
       470 
     | 
    
         
            -
                    raise Dalli::ValueOverMaxSize, message
         
     | 
| 
       471 
     | 
    
         
            -
                  end
         
     | 
| 
       472 
     | 
    
         
            -
             
     | 
| 
       473 
     | 
    
         
            -
                  # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
         
     | 
| 
       474 
     | 
    
         
            -
                  class NilObject; end
         
     | 
| 
       475 
     | 
    
         
            -
                  NOT_FOUND = NilObject.new
         
     | 
| 
       476 
     | 
    
         
            -
             
     | 
| 
       477 
     | 
    
         
            -
                  def generic_response(unpack = false, cache_nils = false)
         
     | 
| 
       478 
     | 
    
         
            -
                    (extras, _, status, count) = read_header.unpack(NORMAL_HEADER)
         
     | 
| 
       479 
     | 
    
         
            -
                    data = read(count) if count > 0
         
     | 
| 
       480 
     | 
    
         
            -
                    if status == 1
         
     | 
| 
       481 
     | 
    
         
            -
                      cache_nils ? NOT_FOUND : nil
         
     | 
| 
       482 
     | 
    
         
            -
                    elsif status == 2 || status == 5
         
     | 
| 
       483 
     | 
    
         
            -
                      false # Not stored, normal status for add operation
         
     | 
| 
       484 
     | 
    
         
            -
                    elsif status != 0
         
     | 
| 
       485 
     | 
    
         
            -
                      raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
         
     | 
| 
       486 
     | 
    
         
            -
                    elsif data
         
     | 
| 
       487 
     | 
    
         
            -
                      flags = data.byteslice(0, extras).unpack1("N")
         
     | 
| 
       488 
     | 
    
         
            -
                      value = data.byteslice(extras, data.bytesize - extras)
         
     | 
| 
       489 
     | 
    
         
            -
                      unpack ? deserialize(value, flags) : value
         
     | 
| 
       490 
     | 
    
         
            -
                    else
         
     | 
| 
       491 
     | 
    
         
            -
                      true
         
     | 
| 
       492 
     | 
    
         
            -
                    end
         
     | 
| 
       493 
339 
     | 
    
         
             
                  end
         
     | 
| 
       494 
340 
     | 
    
         | 
| 
       495 
     | 
    
         
            -
                  def  
     | 
| 
       496 
     | 
    
         
            -
                     
     | 
| 
       497 
     | 
    
         
            -
                     
     | 
| 
       498 
     | 
    
         
            -
                    if  
     | 
| 
       499 
     | 
    
         
            -
             
     | 
| 
       500 
     | 
    
         
            -
             
     | 
| 
       501 
     | 
    
         
            -
             
     | 
| 
       502 
     | 
    
         
            -
                    elsif status != 0
         
     | 
| 
       503 
     | 
    
         
            -
                      raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
         
     | 
| 
       504 
     | 
    
         
            -
                    else
         
     | 
| 
       505 
     | 
    
         
            -
                      cas
         
     | 
| 
       506 
     | 
    
         
            -
                    end
         
     | 
| 
      
 341 
     | 
    
         
            +
                  def connect
         
     | 
| 
      
 342 
     | 
    
         
            +
                    @connection_manager.establish_connection
         
     | 
| 
      
 343 
     | 
    
         
            +
                    authenticate_connection if require_auth?
         
     | 
| 
      
 344 
     | 
    
         
            +
                    @version = version # Connect socket if not authed
         
     | 
| 
      
 345 
     | 
    
         
            +
                    up!
         
     | 
| 
      
 346 
     | 
    
         
            +
                  rescue Dalli::DalliError
         
     | 
| 
      
 347 
     | 
    
         
            +
                    raise
         
     | 
| 
       507 
348 
     | 
    
         
             
                  end
         
     | 
| 
       508 
349 
     | 
    
         | 
| 
       509 
     | 
    
         
            -
                  def  
     | 
| 
       510 
     | 
    
         
            -
                     
     | 
| 
       511 
     | 
    
         
            -
                     
     | 
| 
       512 
     | 
    
         
            -
                      ( 
     | 
| 
       513 
     | 
    
         
            -
                      return hash if key_length == 0
         
     | 
| 
       514 
     | 
    
         
            -
                      key = read(key_length)
         
     | 
| 
       515 
     | 
    
         
            -
                      value = read(body_length - key_length) if body_length - key_length > 0
         
     | 
| 
       516 
     | 
    
         
            -
                      hash[key] = value
         
     | 
| 
      
 350 
     | 
    
         
            +
                  def pipelined_get(keys)
         
     | 
| 
      
 351 
     | 
    
         
            +
                    req = +''
         
     | 
| 
      
 352 
     | 
    
         
            +
                    keys.each do |key|
         
     | 
| 
      
 353 
     | 
    
         
            +
                      req << RequestFormatter.standard_request(opkey: :getkq, key: key)
         
     | 
| 
       517 
354 
     | 
    
         
             
                    end
         
     | 
| 
      
 355 
     | 
    
         
            +
                    # Could send noop here instead of in pipeline_response_setup
         
     | 
| 
      
 356 
     | 
    
         
            +
                    write(req)
         
     | 
| 
       518 
357 
     | 
    
         
             
                  end
         
     | 
| 
       519 
358 
     | 
    
         | 
| 
       520 
     | 
    
         
            -
                  def  
     | 
| 
       521 
     | 
    
         
            -
                     
     | 
| 
       522 
     | 
    
         
            -
                    loop do
         
     | 
| 
       523 
     | 
    
         
            -
                      (key_length, _, body_length, _) = read_header.unpack(KV_HEADER)
         
     | 
| 
       524 
     | 
    
         
            -
                      return hash if key_length == 0
         
     | 
| 
       525 
     | 
    
         
            -
                      flags = read(4).unpack1("N")
         
     | 
| 
       526 
     | 
    
         
            -
                      key = read(key_length)
         
     | 
| 
       527 
     | 
    
         
            -
                      value = read(body_length - key_length - 4) if body_length - key_length - 4 > 0
         
     | 
| 
       528 
     | 
    
         
            -
                      hash[key] = deserialize(value, flags)
         
     | 
| 
       529 
     | 
    
         
            -
                    end
         
     | 
| 
      
 359 
     | 
    
         
            +
                  def response_buffer
         
     | 
| 
      
 360 
     | 
    
         
            +
                    @response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
         
     | 
| 
       530 
361 
     | 
    
         
             
                  end
         
     | 
| 
       531 
362 
     | 
    
         | 
| 
       532 
     | 
    
         
            -
                  def  
     | 
| 
       533 
     | 
    
         
            -
                     
     | 
| 
       534 
     | 
    
         
            -
                    result = @sock.write(bytes)
         
     | 
| 
       535 
     | 
    
         
            -
                    @inprogress = false
         
     | 
| 
       536 
     | 
    
         
            -
                    result
         
     | 
| 
       537 
     | 
    
         
            -
                  rescue SystemCallError, Timeout::Error => e
         
     | 
| 
       538 
     | 
    
         
            -
                    failure!(e)
         
     | 
| 
      
 363 
     | 
    
         
            +
                  def pipeline_response
         
     | 
| 
      
 364 
     | 
    
         
            +
                    response_buffer.process_single_getk_response
         
     | 
| 
       539 
365 
     | 
    
         
             
                  end
         
     | 
| 
       540 
366 
     | 
    
         | 
| 
       541 
     | 
    
         
            -
                   
     | 
| 
       542 
     | 
    
         
            -
             
     | 
| 
       543 
     | 
    
         
            -
             
     | 
| 
       544 
     | 
    
         
            -
                     
     | 
| 
       545 
     | 
    
         
            -
                     
     | 
| 
       546 
     | 
    
         
            -
                  rescue SystemCallError, Timeout::Error, EOFError => e
         
     | 
| 
       547 
     | 
    
         
            -
                    failure!(e)
         
     | 
| 
       548 
     | 
    
         
            -
                  end
         
     | 
| 
      
 367 
     | 
    
         
            +
                  # Called after the noop response is received at the end of a set
         
     | 
| 
      
 368 
     | 
    
         
            +
                  # of pipelined gets
         
     | 
| 
      
 369 
     | 
    
         
            +
                  def finish_pipeline
         
     | 
| 
      
 370 
     | 
    
         
            +
                    response_buffer.clear
         
     | 
| 
      
 371 
     | 
    
         
            +
                    @connection_manager.finish_request!
         
     | 
| 
       549 
372 
     | 
    
         | 
| 
       550 
     | 
    
         
            -
             
     | 
| 
       551 
     | 
    
         
            -
                    read(24) || raise(Dalli::NetworkError, "No response")
         
     | 
| 
      
 373 
     | 
    
         
            +
                    true # to simplify response
         
     | 
| 
       552 
374 
     | 
    
         
             
                  end
         
     | 
| 
       553 
375 
     | 
    
         | 
| 
       554 
     | 
    
         
            -
                  def  
     | 
| 
       555 
     | 
    
         
            -
                     
     | 
| 
       556 
     | 
    
         
            -
             
     | 
| 
       557 
     | 
    
         
            -
                    begin
         
     | 
| 
       558 
     | 
    
         
            -
                      @pid = Process.pid
         
     | 
| 
       559 
     | 
    
         
            -
                      @sock = if socket_type == :unix
         
     | 
| 
       560 
     | 
    
         
            -
                        Dalli::Socket::UNIX.open(hostname, self, options)
         
     | 
| 
       561 
     | 
    
         
            -
                      else
         
     | 
| 
       562 
     | 
    
         
            -
                        Dalli::Socket::TCP.open(hostname, port, self, options)
         
     | 
| 
       563 
     | 
    
         
            -
                      end
         
     | 
| 
       564 
     | 
    
         
            -
                      sasl_authentication if need_auth?
         
     | 
| 
       565 
     | 
    
         
            -
                      @version = version # trigger actual connect
         
     | 
| 
       566 
     | 
    
         
            -
                      up!
         
     | 
| 
       567 
     | 
    
         
            -
                    rescue Dalli::DalliError # SASL auth failure
         
     | 
| 
       568 
     | 
    
         
            -
                      raise
         
     | 
| 
       569 
     | 
    
         
            -
                    rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
         
     | 
| 
       570 
     | 
    
         
            -
                      # SocketError = DNS resolution failure
         
     | 
| 
       571 
     | 
    
         
            -
                      failure!(e)
         
     | 
| 
       572 
     | 
    
         
            -
                    end
         
     | 
| 
      
 376 
     | 
    
         
            +
                  def reconnect_on_pipeline_complete!
         
     | 
| 
      
 377 
     | 
    
         
            +
                    @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
         
     | 
| 
       573 
378 
     | 
    
         
             
                  end
         
     | 
| 
       574 
379 
     | 
    
         | 
| 
       575 
     | 
    
         
            -
                  def  
     | 
| 
       576 
     | 
    
         
            -
                     
     | 
| 
       577 
     | 
    
         
            -
             
     | 
| 
       578 
     | 
    
         
            -
             
     | 
| 
       579 
     | 
    
         
            -
                  REQUEST = 0x80
         
     | 
| 
       580 
     | 
    
         
            -
                  RESPONSE = 0x81
         
     | 
| 
       581 
     | 
    
         
            -
             
     | 
| 
       582 
     | 
    
         
            -
                  # Response codes taken from:
         
     | 
| 
       583 
     | 
    
         
            -
                  # https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
         
     | 
| 
       584 
     | 
    
         
            -
                  RESPONSE_CODES = {
         
     | 
| 
       585 
     | 
    
         
            -
                    0 => "No error",
         
     | 
| 
       586 
     | 
    
         
            -
                    1 => "Key not found",
         
     | 
| 
       587 
     | 
    
         
            -
                    2 => "Key exists",
         
     | 
| 
       588 
     | 
    
         
            -
                    3 => "Value too large",
         
     | 
| 
       589 
     | 
    
         
            -
                    4 => "Invalid arguments",
         
     | 
| 
       590 
     | 
    
         
            -
                    5 => "Item not stored",
         
     | 
| 
       591 
     | 
    
         
            -
                    6 => "Incr/decr on a non-numeric value",
         
     | 
| 
       592 
     | 
    
         
            -
                    7 => "The vbucket belongs to another server",
         
     | 
| 
       593 
     | 
    
         
            -
                    8 => "Authentication error",
         
     | 
| 
       594 
     | 
    
         
            -
                    9 => "Authentication continue",
         
     | 
| 
       595 
     | 
    
         
            -
                    0x20 => "Authentication required",
         
     | 
| 
       596 
     | 
    
         
            -
                    0x81 => "Unknown command",
         
     | 
| 
       597 
     | 
    
         
            -
                    0x82 => "Out of memory",
         
     | 
| 
       598 
     | 
    
         
            -
                    0x83 => "Not supported",
         
     | 
| 
       599 
     | 
    
         
            -
                    0x84 => "Internal error",
         
     | 
| 
       600 
     | 
    
         
            -
                    0x85 => "Busy",
         
     | 
| 
       601 
     | 
    
         
            -
                    0x86 => "Temporary failure"
         
     | 
| 
       602 
     | 
    
         
            -
                  }
         
     | 
| 
       603 
     | 
    
         
            -
             
     | 
| 
       604 
     | 
    
         
            -
                  OPCODES = {
         
     | 
| 
       605 
     | 
    
         
            -
                    get: 0x00,
         
     | 
| 
       606 
     | 
    
         
            -
                    set: 0x01,
         
     | 
| 
       607 
     | 
    
         
            -
                    add: 0x02,
         
     | 
| 
       608 
     | 
    
         
            -
                    replace: 0x03,
         
     | 
| 
       609 
     | 
    
         
            -
                    delete: 0x04,
         
     | 
| 
       610 
     | 
    
         
            -
                    incr: 0x05,
         
     | 
| 
       611 
     | 
    
         
            -
                    decr: 0x06,
         
     | 
| 
       612 
     | 
    
         
            -
                    flush: 0x08,
         
     | 
| 
       613 
     | 
    
         
            -
                    noop: 0x0A,
         
     | 
| 
       614 
     | 
    
         
            -
                    version: 0x0B,
         
     | 
| 
       615 
     | 
    
         
            -
                    getkq: 0x0D,
         
     | 
| 
       616 
     | 
    
         
            -
                    append: 0x0E,
         
     | 
| 
       617 
     | 
    
         
            -
                    prepend: 0x0F,
         
     | 
| 
       618 
     | 
    
         
            -
                    stat: 0x10,
         
     | 
| 
       619 
     | 
    
         
            -
                    setq: 0x11,
         
     | 
| 
       620 
     | 
    
         
            -
                    addq: 0x12,
         
     | 
| 
       621 
     | 
    
         
            -
                    replaceq: 0x13,
         
     | 
| 
       622 
     | 
    
         
            -
                    deleteq: 0x14,
         
     | 
| 
       623 
     | 
    
         
            -
                    incrq: 0x15,
         
     | 
| 
       624 
     | 
    
         
            -
                    decrq: 0x16,
         
     | 
| 
       625 
     | 
    
         
            -
                    auth_negotiation: 0x20,
         
     | 
| 
       626 
     | 
    
         
            -
                    auth_request: 0x21,
         
     | 
| 
       627 
     | 
    
         
            -
                    auth_continue: 0x22,
         
     | 
| 
       628 
     | 
    
         
            -
                    touch: 0x1C,
         
     | 
| 
       629 
     | 
    
         
            -
                    gat: 0x1D
         
     | 
| 
       630 
     | 
    
         
            -
                  }
         
     | 
| 
       631 
     | 
    
         
            -
             
     | 
| 
       632 
     | 
    
         
            -
                  HEADER = "CCnCCnNNQ"
         
     | 
| 
       633 
     | 
    
         
            -
                  OP_FORMAT = {
         
     | 
| 
       634 
     | 
    
         
            -
                    get: "a*",
         
     | 
| 
       635 
     | 
    
         
            -
                    set: "NNa*a*",
         
     | 
| 
       636 
     | 
    
         
            -
                    add: "NNa*a*",
         
     | 
| 
       637 
     | 
    
         
            -
                    replace: "NNa*a*",
         
     | 
| 
       638 
     | 
    
         
            -
                    delete: "a*",
         
     | 
| 
       639 
     | 
    
         
            -
                    incr: "NNNNNa*",
         
     | 
| 
       640 
     | 
    
         
            -
                    decr: "NNNNNa*",
         
     | 
| 
       641 
     | 
    
         
            -
                    flush: "N",
         
     | 
| 
       642 
     | 
    
         
            -
                    noop: "",
         
     | 
| 
       643 
     | 
    
         
            -
                    getkq: "a*",
         
     | 
| 
       644 
     | 
    
         
            -
                    version: "",
         
     | 
| 
       645 
     | 
    
         
            -
                    stat: "a*",
         
     | 
| 
       646 
     | 
    
         
            -
                    append: "a*a*",
         
     | 
| 
       647 
     | 
    
         
            -
                    prepend: "a*a*",
         
     | 
| 
       648 
     | 
    
         
            -
                    auth_request: "a*a*",
         
     | 
| 
       649 
     | 
    
         
            -
                    auth_continue: "a*a*",
         
     | 
| 
       650 
     | 
    
         
            -
                    touch: "Na*",
         
     | 
| 
       651 
     | 
    
         
            -
                    gat: "Na*"
         
     | 
| 
       652 
     | 
    
         
            -
                  }
         
     | 
| 
       653 
     | 
    
         
            -
                  FORMAT = OP_FORMAT.each_with_object({}) { |(k, v), memo| memo[k] = HEADER + v; }
         
     | 
| 
       654 
     | 
    
         
            -
             
     | 
| 
       655 
     | 
    
         
            -
                  #######
         
     | 
| 
       656 
     | 
    
         
            -
                  # SASL authentication support for NorthScale
         
     | 
| 
       657 
     | 
    
         
            -
                  #######
         
     | 
| 
       658 
     | 
    
         
            -
             
     | 
| 
       659 
     | 
    
         
            -
                  def need_auth?
         
     | 
| 
       660 
     | 
    
         
            -
                    @options[:username] || ENV["MEMCACHE_USERNAME"]
         
     | 
| 
      
 380 
     | 
    
         
            +
                  def log_marshal_err(key, err)
         
     | 
| 
      
 381 
     | 
    
         
            +
                    Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
         
     | 
| 
      
 382 
     | 
    
         
            +
                    Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
         
     | 
| 
       661 
383 
     | 
    
         
             
                  end
         
     | 
| 
       662 
384 
     | 
    
         | 
| 
       663 
     | 
    
         
            -
                  def  
     | 
| 
       664 
     | 
    
         
            -
                     
     | 
| 
       665 
     | 
    
         
            -
             
     | 
| 
       666 
     | 
    
         
            -
             
     | 
| 
       667 
     | 
    
         
            -
                  def password
         
     | 
| 
       668 
     | 
    
         
            -
                    @options[:password] || ENV["MEMCACHE_PASSWORD"]
         
     | 
| 
      
 385 
     | 
    
         
            +
                  def log_unexpected_err(err)
         
     | 
| 
      
 386 
     | 
    
         
            +
                    Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
         
     | 
| 
      
 387 
     | 
    
         
            +
                    Dalli.logger.error err.backtrace.join("\n\t")
         
     | 
| 
       669 
388 
     | 
    
         
             
                  end
         
     | 
| 
       670 
389 
     | 
    
         | 
| 
       671 
     | 
    
         
            -
                   
     | 
| 
       672 
     | 
    
         
            -
                    Dalli.logger.info { "Dalli/SASL authenticating as #{username}" }
         
     | 
| 
       673 
     | 
    
         
            -
             
     | 
| 
       674 
     | 
    
         
            -
                    # negotiate
         
     | 
| 
       675 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
         
     | 
| 
       676 
     | 
    
         
            -
                    write(req)
         
     | 
| 
       677 
     | 
    
         
            -
             
     | 
| 
       678 
     | 
    
         
            -
                    (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
         
     | 
| 
       679 
     | 
    
         
            -
                    raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
         
     | 
| 
       680 
     | 
    
         
            -
                    content = read(count).tr("\u0000", " ")
         
     | 
| 
       681 
     | 
    
         
            -
                    return Dalli.logger.debug("Authentication not required/supported by server") if status == 0x81
         
     | 
| 
       682 
     | 
    
         
            -
                    mechanisms = content.split(" ")
         
     | 
| 
       683 
     | 
    
         
            -
                    raise NotImplementedError, "Dalli only supports the PLAIN authentication mechanism" unless mechanisms.include?("PLAIN")
         
     | 
| 
       684 
     | 
    
         
            -
             
     | 
| 
       685 
     | 
    
         
            -
                    # request
         
     | 
| 
       686 
     | 
    
         
            -
                    mechanism = "PLAIN"
         
     | 
| 
       687 
     | 
    
         
            -
                    msg = "\x0#{username}\x0#{password}"
         
     | 
| 
       688 
     | 
    
         
            -
                    req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
         
     | 
| 
       689 
     | 
    
         
            -
                    write(req)
         
     | 
| 
       690 
     | 
    
         
            -
             
     | 
| 
       691 
     | 
    
         
            -
                    (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
         
     | 
| 
       692 
     | 
    
         
            -
                    raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
         
     | 
| 
       693 
     | 
    
         
            -
                    content = read(count)
         
     | 
| 
       694 
     | 
    
         
            -
                    return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
         
     | 
| 
       695 
     | 
    
         
            -
             
     | 
| 
       696 
     | 
    
         
            -
                    raise Dalli::DalliError, "Error authenticating: #{status}" unless status == 0x21
         
     | 
| 
       697 
     | 
    
         
            -
                    raise NotImplementedError, "No two-step authentication mechanisms supported"
         
     | 
| 
       698 
     | 
    
         
            -
                    # (step, msg) = sasl.receive('challenge', content)
         
     | 
| 
       699 
     | 
    
         
            -
                    # raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
         
     | 
| 
       700 
     | 
    
         
            -
                  end
         
     | 
| 
      
 390 
     | 
    
         
            +
                  include SaslAuthentication
         
     | 
| 
       701 
391 
     | 
    
         
             
                end
         
     | 
| 
       702 
392 
     | 
    
         
             
              end
         
     | 
| 
       703 
393 
     | 
    
         
             
            end
         
     |