aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/vendor/memcache-client-1.5.1
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2008-12-09 11:13:02 +0100
committerDavid Heinemeier Hansson <david@loudthinking.com>2008-12-09 11:13:56 +0100
commite54f17920fddebdfa241a5d9253ad5810972af87 (patch)
tree9f59954743619e0478c359849905585d150e793e /activesupport/lib/active_support/vendor/memcache-client-1.5.1
parent9adcf951ea7e5342c913dda40594cbb382995e3b (diff)
downloadrails-e54f17920fddebdfa241a5d9253ad5810972af87.tar.gz
rails-e54f17920fddebdfa241a5d9253ad5810972af87.tar.bz2
rails-e54f17920fddebdfa241a5d9253ad5810972af87.zip
Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts (Joshua Sierles) [#1535 state:committed]
Signed-off-by: David Heinemeier Hansson <david@loudthinking.com>
Diffstat (limited to 'activesupport/lib/active_support/vendor/memcache-client-1.5.1')
-rw-r--r--activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb849
1 files changed, 0 insertions, 849 deletions
diff --git a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb
deleted file mode 100644
index 99c9af0398..0000000000
--- a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb
+++ /dev/null
@@ -1,849 +0,0 @@
-# All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel,
-# The Robot Co-op. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. Neither the names of the authors nor the names of their contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
-# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-require 'socket'
-require 'thread'
-require 'timeout'
-require 'rubygems'
-
-class String
-
- ##
- # Uses the ITU-T polynomial in the CRC32 algorithm.
-
- def crc32_ITU_T
- n = length
- r = 0xFFFFFFFF
-
- n.times do |i|
- r ^= self[i]
- 8.times do
- if (r & 1) != 0 then
- r = (r>>1) ^ 0xEDB88320
- else
- r >>= 1
- end
- end
- end
-
- r ^ 0xFFFFFFFF
- end
-
-end
-
-##
-# A Ruby client library for memcached.
-#
-# This is intended to provide access to basic memcached functionality. It
-# does not attempt to be complete implementation of the entire API, but it is
-# approaching a complete implementation.
-
-class MemCache
-
- ##
- # The version of MemCache you are using.
-
- VERSION = '1.5.0'
-
- ##
- # Default options for the cache object.
-
- DEFAULT_OPTIONS = {
- :namespace => nil,
- :readonly => false,
- :multithread => false,
- }
-
- ##
- # Default memcached port.
-
- DEFAULT_PORT = 11211
-
- ##
- # Default memcached server weight.
-
- DEFAULT_WEIGHT = 1
-
- ##
- # The amount of time to wait for a response from a memcached server. If a
- # response is not completed within this time, the connection to the server
- # will be closed and an error will be raised.
-
- attr_accessor :request_timeout
-
- ##
- # The namespace for this instance
-
- attr_reader :namespace
-
- ##
- # The multithread setting for this instance
-
- attr_reader :multithread
-
- ##
- # The servers this client talks to. Play at your own peril.
-
- attr_reader :servers
-
- ##
- # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
- # omitted. See +servers=+ for acceptable server list arguments.
- #
- # Valid options for +opts+ are:
- #
- # [:namespace] Prepends this value to all keys added or retrieved.
- # [:readonly] Raises an exception on cache writes when true.
- # [:multithread] Wraps cache access in a Mutex for thread safety.
- #
- # Other options are ignored.
-
- def initialize(*args)
- servers = []
- opts = {}
-
- case args.length
- when 0 then # NOP
- when 1 then
- arg = args.shift
- case arg
- when Hash then opts = arg
- when Array then servers = arg
- when String then servers = [arg]
- else raise ArgumentError, 'first argument must be Array, Hash or String'
- end
- when 2 then
- servers, opts = args
- else
- raise ArgumentError, "wrong number of arguments (#{args.length} for 2)"
- end
-
- opts = DEFAULT_OPTIONS.merge opts
- @namespace = opts[:namespace]
- @readonly = opts[:readonly]
- @multithread = opts[:multithread]
- @mutex = Mutex.new if @multithread
- @buckets = []
- self.servers = servers
- end
-
- ##
- # Returns a string representation of the cache object.
-
- def inspect
- "<MemCache: %d servers, %d buckets, ns: %p, ro: %p>" %
- [@servers.length, @buckets.length, @namespace, @readonly]
- end
-
- ##
- # Returns whether there is at least one active server for the object.
-
- def active?
- not @servers.empty?
- end
-
- ##
- # Returns whether or not the cache object was created read only.
-
- def readonly?
- @readonly
- end
-
- ##
- # Set the servers that the requests will be distributed between. Entries
- # can be either strings of the form "hostname:port" or
- # "hostname:port:weight" or MemCache::Server objects.
-
- def servers=(servers)
- # Create the server objects.
- @servers = servers.collect do |server|
- case server
- when String
- host, port, weight = server.split ':', 3
- port ||= DEFAULT_PORT
- weight ||= DEFAULT_WEIGHT
- Server.new self, host, port, weight
- when Server
- if server.memcache.multithread != @multithread then
- raise ArgumentError, "can't mix threaded and non-threaded servers"
- end
- server
- else
- raise TypeError, "cannot convert #{server.class} into MemCache::Server"
- end
- end
-
- # Create an array of server buckets for weight selection of servers.
- @buckets = []
- @servers.each do |server|
- server.weight.times { @buckets.push(server) }
- end
- end
-
- ##
- # Decrements the value for +key+ by +amount+ and returns the new value.
- # +key+ must already exist. If +key+ is not an integer, it is assumed to be
- # 0. +key+ can not be decremented below 0.
-
- def decr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_decr server, cache_key, amount
- else
- cache_decr server, cache_key, amount
- end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
- end
-
- ##
- # Retrieves +key+ from memcache. If +raw+ is false, the value will be
- # unmarshalled.
-
- def get(key, raw = false)
- server, cache_key = request_setup key
-
- value = if @multithread then
- threadsafe_cache_get server, cache_key
- else
- cache_get server, cache_key
- end
-
- return nil if value.nil?
-
- value = Marshal.load value unless raw
-
- return value
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
- end
-
- ##
- # Retrieves multiple values from memcached in parallel, if possible.
- #
- # The memcached protocol supports the ability to retrieve multiple
- # keys in a single request. Pass in an array of keys to this method
- # and it will:
- #
- # 1. map the key to the appropriate memcached server
- # 2. send a single request to each server that has one or more key values
- #
- # Returns a hash of values.
- #
- # cache["a"] = 1
- # cache["b"] = 2
- # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
-
- def get_multi(*keys)
- raise MemCacheError, 'No active servers' unless active?
-
- keys.flatten!
- key_count = keys.length
- cache_keys = {}
- server_keys = Hash.new { |h,k| h[k] = [] }
-
- # map keys to servers
- keys.each do |key|
- server, cache_key = request_setup key
- cache_keys[cache_key] = key
- server_keys[server] << cache_key
- end
-
- results = {}
-
- server_keys.each do |server, keys_for_server|
- keys_for_server = keys_for_server.join ' '
- values = if @multithread then
- threadsafe_cache_get_multi server, keys_for_server
- else
- cache_get_multi server, keys_for_server
- end
- values.each do |key, value|
- results[cache_keys[key]] = Marshal.load value
- end
- end
-
- return results
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
- end
-
- ##
- # Increments the value for +key+ by +amount+ and retruns the new value.
- # +key+ must already exist. If +key+ is not an integer, it is assumed to be
- # 0.
-
- def incr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_incr server, cache_key, amount
- else
- cache_incr server, cache_key, amount
- end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
- end
-
- ##
- # Add +key+ to the cache with value +value+ that expires in +expiry+
- # seconds. If +raw+ is true, +value+ will not be Marshalled.
- #
- # Warning: Readers should not call this method in the event of a cache miss;
- # see MemCache#add.
-
- def set(key, value, expiry = 0, raw = false)
- raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
-
- value = Marshal.dump value unless raw
- command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
-
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
- end
- end
-
- ##
- # Add +key+ to the cache with value +value+ that expires in +expiry+
- # seconds, but only if +key+ does not already exist in the cache.
- # If +raw+ is true, +value+ will not be Marshalled.
- #
- # Readers should call this method in the event of a cache miss, not
- # MemCache#set or MemCache#[]=.
-
- def add(key, value, expiry = 0, raw = false)
- raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
-
- value = Marshal.dump value unless raw
- command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
-
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
- end
- end
-
- ##
- # Removes +key+ from the cache in +expiry+ seconds.
-
- def delete(key, expiry = 0)
- @mutex.lock if @multithread
-
- raise MemCacheError, "No active servers" unless active?
- cache_key = make_cache_key key
- server = get_server_for_key cache_key
-
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
-
- begin
- sock.write "delete #{cache_key} #{expiry}\r\n"
- result = sock.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- end
- ensure
- @mutex.unlock if @multithread
- end
-
- ##
- # Flush the cache from all memcache servers.
-
- def flush_all
- raise MemCacheError, 'No active servers' unless active?
- raise MemCacheError, "Update of readonly cache" if @readonly
- begin
- @mutex.lock if @multithread
- @servers.each do |server|
- begin
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
- sock.write "flush_all\r\n"
- result = sock.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- end
- end
- ensure
- @mutex.unlock if @multithread
- end
- end
-
- ##
- # Reset the connection to all memcache servers. This should be called if
- # there is a problem with a cache lookup that might have left the connection
- # in a corrupted state.
-
- def reset
- @servers.each { |server| server.close }
- end
-
- ##
- # Returns statistics for each memcached server. An explanation of the
- # statistics can be found in the memcached docs:
- #
- # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
- #
- # Example:
- #
- # >> pp CACHE.stats
- # {"localhost:11211"=>
- # {"bytes"=>4718,
- # "pid"=>20188,
- # "connection_structures"=>4,
- # "time"=>1162278121,
- # "pointer_size"=>32,
- # "limit_maxbytes"=>67108864,
- # "cmd_get"=>14532,
- # "version"=>"1.2.0",
- # "bytes_written"=>432583,
- # "cmd_set"=>32,
- # "get_misses"=>0,
- # "total_connections"=>19,
- # "curr_connections"=>3,
- # "curr_items"=>4,
- # "uptime"=>1557,
- # "get_hits"=>14532,
- # "total_items"=>32,
- # "rusage_system"=>0.313952,
- # "rusage_user"=>0.119981,
- # "bytes_read"=>190619}}
- # => nil
-
- def stats
- raise MemCacheError, "No active servers" unless active?
- server_stats = {}
-
- @servers.each do |server|
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
-
- value = nil
- begin
- sock.write "stats\r\n"
- stats = {}
- while line = sock.gets do
- raise_on_error_response! line
- break if line == "END\r\n"
- if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then
- name, value = $1, $2
- stats[name] = case name
- when 'version'
- value
- when 'rusage_user', 'rusage_system' then
- seconds, microseconds = value.split(/:/, 2)
- microseconds ||= 0
- Float(seconds) + (Float(microseconds) / 1_000_000)
- else
- if value =~ /\A\d+\Z/ then
- value.to_i
- else
- value
- end
- end
- end
- end
- server_stats["#{server.host}:#{server.port}"] = stats
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- end
- end
-
- server_stats
- end
-
- ##
- # Shortcut to get a value from the cache.
-
- alias [] get
-
- ##
- # Shortcut to save a value in the cache. This method does not set an
- # expiration on the entry. Use set to specify an explicit expiry.
-
- def []=(key, value)
- set key, value
- end
-
- protected
-
- ##
- # Create a key for the cache, incorporating the namespace qualifier if
- # requested.
-
- def make_cache_key(key)
- if namespace.nil? then
- key
- else
- "#{@namespace}:#{key}"
- end
- end
-
- ##
- # Pick a server to handle the request based on a hash of the key.
-
- def get_server_for_key(key)
- raise ArgumentError, "illegal character in key #{key.inspect}" if
- key =~ /\s/
- raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
- raise MemCacheError, "No servers available" if @servers.empty?
- return @servers.first if @servers.length == 1
-
- hkey = hash_for key
-
- 20.times do |try|
- server = @buckets[hkey % @buckets.nitems]
- return server if server.alive?
- hkey += hash_for "#{try}#{key}"
- end
-
- raise MemCacheError, "No servers available"
- end
-
- ##
- # Returns an interoperable hash value for +key+. (I think, docs are
- # sketchy for down servers).
-
- def hash_for(key)
- (key.crc32_ITU_T >> 16) & 0x7fff
- end
-
- ##
- # Performs a raw decr for +cache_key+ from +server+. Returns nil if not
- # found.
-
- def cache_decr(server, cache_key, amount)
- socket = server.socket
- socket.write "decr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
- end
-
- ##
- # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache
- # miss.
-
- def cache_get(server, cache_key)
- socket = server.socket
- socket.write "get #{cache_key}\r\n"
- keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
-
- if keyline.nil? then
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
- end
-
- raise_on_error_response! keyline
- return nil if keyline == "END\r\n"
-
- unless keyline =~ /(\d+)\r/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
- end
- value = socket.read $1.to_i
- socket.read 2 # "\r\n"
- socket.gets # "END\r\n"
- return value
- end
-
- ##
- # Fetches +cache_keys+ from +server+ using a multi-get.
-
- def cache_get_multi(server, cache_keys)
- values = {}
- socket = server.socket
- socket.write "get #{cache_keys}\r\n"
-
- while keyline = socket.gets do
- return values if keyline == "END\r\n"
- raise_on_error_response! keyline
-
- unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
- end
-
- key, data_length = $1, $3
- values[$1] = socket.read data_length.to_i
- socket.read(2) # "\r\n"
- end
-
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
- end
-
- ##
- # Performs a raw incr for +cache_key+ from +server+. Returns nil if not
- # found.
-
- def cache_incr(server, cache_key, amount)
- socket = server.socket
- socket.write "incr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
- end
-
- ##
- # Handles +error+ from +server+.
-
- def handle_error(server, error)
- server.close if server
- new_error = MemCacheError.new error.message
- new_error.set_backtrace error.backtrace
- raise new_error
- end
-
- ##
- # Performs setup for making a request with +key+ from memcached. Returns
- # the server to fetch the key from and the complete key to use.
-
- def request_setup(key)
- raise MemCacheError, 'No active servers' unless active?
- cache_key = make_cache_key key
- server = get_server_for_key cache_key
- raise MemCacheError, 'No connection to server' if server.socket.nil?
- return server, cache_key
- end
-
- def threadsafe_cache_decr(server, cache_key, amount) # :nodoc:
- @mutex.lock
- cache_decr server, cache_key, amount
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_get(server, cache_key) # :nodoc:
- @mutex.lock
- cache_get server, cache_key
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc:
- @mutex.lock
- cache_get_multi socket, cache_keys
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_incr(server, cache_key, amount) # :nodoc:
- @mutex.lock
- cache_incr server, cache_key, amount
- ensure
- @mutex.unlock
- end
-
- def raise_on_error_response!(response)
- if response =~ /\A(?:CLIENT_|SERVER_)?ERROR (.*)/
- raise MemCacheError, $1.strip
- end
- end
-
-
- ##
- # This class represents a memcached server instance.
-
- class Server
-
- ##
- # The amount of time to wait to establish a connection with a memcached
- # server. If a connection cannot be established within this time limit,
- # the server will be marked as down.
-
- CONNECT_TIMEOUT = 0.25
-
- ##
- # The amount of time to wait before attempting to re-establish a
- # connection with a server that is marked dead.
-
- RETRY_DELAY = 30.0
-
- ##
- # The host the memcached server is running on.
-
- attr_reader :host
-
- ##
- # The port the memcached server is listening on.
-
- attr_reader :port
-
- ##
- # The weight given to the server.
-
- attr_reader :weight
-
- ##
- # The time of next retry if the connection is dead.
-
- attr_reader :retry
-
- ##
- # A text status string describing the state of the server.
-
- attr_reader :status
-
- ##
- # Create a new MemCache::Server object for the memcached instance
- # listening on the given host and port, weighted by the given weight.
-
- def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT)
- raise ArgumentError, "No host specified" if host.nil? or host.empty?
- raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
-
- @memcache = memcache
- @host = host
- @port = port.to_i
- @weight = weight.to_i
-
- @multithread = @memcache.multithread
- @mutex = Mutex.new
-
- @sock = nil
- @retry = nil
- @status = 'NOT CONNECTED'
- end
-
- ##
- # Return a string representation of the server object.
-
- def inspect
- "<MemCache::Server: %s:%d [%d] (%s)>" % [@host, @port, @weight, @status]
- end
-
- ##
- # Check whether the server connection is alive. This will cause the
- # socket to attempt to connect if it isn't already connected and or if
- # the server was previously marked as down and the retry time has
- # been exceeded.
-
- def alive?
- !!socket
- end
-
- ##
- # Try to connect to the memcached server targeted by this object.
- # Returns the connected socket object on success or nil on failure.
-
- def socket
- @mutex.lock if @multithread
- return @sock if @sock and not @sock.closed?
-
- @sock = nil
-
- # If the host was dead, don't retry for a while.
- return if @retry and @retry > Time.now
-
- # Attempt to connect if not already connected.
- begin
- @sock = timeout CONNECT_TIMEOUT do
- TCPSocket.new @host, @port
- end
- if Socket.constants.include? 'TCP_NODELAY' then
- @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
- end
- @retry = nil
- @status = 'CONNECTED'
- rescue SocketError, SystemCallError, IOError, Timeout::Error => err
- mark_dead err.message
- end
-
- return @sock
- ensure
- @mutex.unlock if @multithread
- end
-
- ##
- # Close the connection to the memcached server targeted by this
- # object. The server is not considered dead.
-
- def close
- @mutex.lock if @multithread
- @sock.close if @sock && !@sock.closed?
- @sock = nil
- @retry = nil
- @status = "NOT CONNECTED"
- ensure
- @mutex.unlock if @multithread
- end
-
- private
-
- ##
- # Mark the server as dead and close its socket.
-
- def mark_dead(reason = "Unknown error")
- @sock.close if @sock && !@sock.closed?
- @sock = nil
- @retry = Time.now + RETRY_DELAY
-
- @status = sprintf "DEAD: %s, will retry at %s", reason, @retry
- end
- end
-
- ##
- # Base MemCache exception class.
-
- class MemCacheError < RuntimeError; end
-
-end
-