lib/dalli/client.rb in dalli-2.7.4 vs lib/dalli/client.rb in dalli-2.7.5

- old
+ new

@@ -25,10 +25,11 @@ # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true. # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever # - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before sending them to memcached. # - :serializer - defaults to Marshal # - :compressor - defaults to zlib + # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for #fetch operations. # def initialize(servers=nil, options={}) @servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211') @options = normalize_options(options) @ring = nil @@ -50,12 +51,13 @@ Thread.current[:dalli_multi] = old end ## # Get the value associated with the key. + # If a value is not found, then +nil+ is returned. def get(key, options=nil) - perform(:get, key) + perform(:get, key, options) end ## # Fetch multiple keys efficiently. # If a block is given, yields key/value pairs one at a time. @@ -69,16 +71,29 @@ get_multi_yielder(keys) {|k, data| hash[k] = data.first} end end end + CACHE_NILS = {cache_nils: true}.freeze + + # Fetch the value associated with the key. + # If a value is found, then it is returned. + # + # If a value is not found and no block is given, then nil is returned. + # + # If a value is not found (or if the found value is nil and :cache_nils is false) + # and a block is given, the block will be invoked and its return value + # written to the cache and returned. def fetch(key, ttl=nil, options=nil) - ttl ||= @options[:expires_in].to_i + options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils] val = get(key, options) - if val.nil? && block_given? + not_found = @options[:cache_nils] ? + val == Dalli::Server::NOT_FOUND : + val.nil? + if not_found && block_given? val = yield - add(key, val, ttl, options) + add(key, val, ttl_or_default(ttl), options) end val end ## @@ -91,38 +106,34 @@ # Returns: # - nil if the key did not exist. # - false if the value was changed by someone else. # - true if the value was successfully updated. def cas(key, ttl=nil, options=nil) - ttl ||= @options[:expires_in].to_i (value, cas) = perform(:cas, key) value = (!value || value == 'Not found') ? nil : value if value newvalue = yield(value) - perform(:set, key, newvalue, ttl, cas, options) + perform(:set, key, newvalue, ttl_or_default(ttl), cas, options) end end def set(key, value, ttl=nil, options=nil) - ttl ||= @options[:expires_in].to_i - perform(:set, key, value, ttl, 0, options) + perform(:set, key, value, ttl_or_default(ttl), 0, options) end ## # Conditionally add a key/value pair, if the key does not already exist # on the server. Returns truthy if the operation succeeded. def add(key, value, ttl=nil, options=nil) - ttl ||= @options[:expires_in].to_i - perform(:add, key, value, ttl, options) + perform(:add, key, value, ttl_or_default(ttl), options) end ## # Conditionally add a key/value pair, only if the key already exists # on the server. Returns truthy if the operation succeeded. def replace(key, value, ttl=nil, options=nil) - ttl ||= @options[:expires_in].to_i - perform(:replace, key, value, ttl, 0, options) + perform(:replace, key, value, ttl_or_default(ttl), 0, options) end def delete(key) perform(:delete, key, 0) end @@ -159,12 +170,11 @@ # Note that the ttl will only apply if the counter does not already # exist. To increase an existing counter and update its TTL, use # #cas. def incr(key, amt=1, ttl=nil, default=nil) raise ArgumentError, "Positive values only: #{amt}" if amt < 0 - ttl ||= @options[:expires_in].to_i - perform(:incr, key, amt.to_i, ttl, default) + perform(:incr, key, amt.to_i, ttl_or_default(ttl), default) end ## # Decr subtracts the given amount from the counter on the memcached server. # Amt must be a positive integer value. @@ -179,21 +189,19 @@ # Note that the ttl will only apply if the counter does not already # exist. To decrease an existing counter and update its TTL, use # #cas. def decr(key, amt=1, ttl=nil, default=nil) raise ArgumentError, "Positive values only: #{amt}" if amt < 0 - ttl ||= @options[:expires_in].to_i - perform(:decr, key, amt.to_i, ttl, default) + perform(:decr, key, amt.to_i, ttl_or_default(ttl), default) end ## # Touch updates expiration time for a given key. # # Returns true if key exists, otherwise nil. def touch(key, ttl=nil) - ttl ||= @options[:expires_in].to_i - resp = perform(:touch, key, ttl) + resp = perform(:touch, key, ttl_or_default(ttl)) resp.nil? ? nil : true end ## # Collect the stats for each server. @@ -248,10 +256,16 @@ yield self end private + def ttl_or_default(ttl) + (ttl || @options[:expires_in]).to_i + rescue NoMethodError + raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer" + end + def groups_for_keys(*keys) groups = mapped_keys(keys).flatten.group_by do |key| begin ring.server_for_key(key) rescue Dalli::RingError @@ -294,12 +308,13 @@ end servers end ## - # Normalizes the argument into an array of servers. If the argument is a string, it's expected to be of - # the format "memcache1.example.com:11211[,memcache2.example.com:11211[,memcache3.example.com:11211[...]]] + # Normalizes the argument into an array of servers. + # If the argument is a string, it's expected that the URIs are comma separated e.g. + # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211" def normalize_servers(servers) if servers.is_a? String return servers.split(",") else return servers @@ -352,11 +367,11 @@ def key_with_namespace(key) (ns = namespace) ? "#{ns}:#{key}" : key end def key_without_namespace(key) - (ns = namespace) ? key.sub(%r(\A#{ns}:), '') : key + (ns = namespace) ? key.sub(%r(\A#{Regexp.escape ns}:), '') : key end def namespace return nil unless @options[:namespace] @options[:namespace].is_a?(Proc) ? @options[:namespace].call.to_s : @options[:namespace].to_s @@ -399,15 +414,13 @@ break if servers.empty? # calculate remaining timeout elapsed = Time.now - start timeout = servers.first.options[:socket_timeout] - if elapsed > timeout - readable = nil - else - sockets = servers.map(&:sock) - readable, _ = IO.select(sockets, nil, nil, timeout - elapsed) - end + time_left = (elapsed > timeout) ? 0 : timeout - elapsed + + sockets = servers.map(&:sock) + readable, _ = IO.select(sockets, nil, nil, time_left) if readable.nil? # no response within timeout; abort pending connections servers.each do |server| Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }