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" }