class ActiveSupport::Cache::LibmemcachedStore

Store using memcached gem as client

Global options can be passed to be applied to each method by default. Supported options are

value, default is 4K

Specific value can be passed per key with write and fetch command.

Options can also be passed direclty to the memcache client, via the :client option. For example, if you want to use pipelining, you can use :client => { :no_block => true }

Constants

DEFAULT_CLIENT_OPTIONS
DEFAULT_COMPRESS_THRESHOLD
ESCAPE_KEY_CHARS
FLAG_COMPRESSED

Attributes

addresses[R]
options[R]
silence[R]
silence?[R]

Public Class Methods

new(*addresses) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 67
def initialize(*addresses)
  addresses.flatten!
  options = addresses.extract_options!
  client_options = options.delete(:client) || {}
  if options[:namespace]
    client_options[:prefix_key] = options.delete(:namespace)
    client_options[:prefix_delimiter] = ':'
  end
  @namespace_length = "#{client_options[:prefix_key]}#{client_options[:prefix_delimiter]}".force_encoding("BINARY").size

  client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]

  @options = {compress_threshold: DEFAULT_COMPRESS_THRESHOLD}.merge(options)
  @addresses = addresses
  @cache = ::LibmemcachedStore::MemcachedWithFlags.new(@addresses, DEFAULT_CLIENT_OPTIONS.merge(client_options))
end

Public Instance Methods

clear(options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 200
def clear(options = nil)
  instrument(:clear, "*") do
    @cache.flush
  end
end
decrement(key, amount = 1, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 166
def decrement(key, amount = 1, options = nil)
  key = normalize_key(key)
  instrument(:decrement, key, amount: amount) do
    @cache.decr(key, amount)
  end
rescue Memcached::NotFound
  nil
rescue Memcached::Error => e
  log_error(e)
  nil
end
delete(key, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 133
def delete(key, options = nil)
  key = normalize_key(key)
  instrument(:delete, key) do |payload|
    delete_entry(key, options)
  end
end
exist?(key, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 140
def exist?(key, options = nil)
  key = normalize_key(key)
  instrument(:exist?, key) do |payload|
    if @cache.respond_to?(:exist)
      @cache.exist(key)
      true
    else
      read_entry(key, options) != nil
    end
  end
rescue Memcached::NotFound
  false
end
fetch(key, options = nil) { || ... } click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 84
def fetch(key, options = nil, &block)
  if block_given?
    if options && options[:race_condition_ttl] && options[:expires_in]
      fetch_with_race_condition_ttl(key, options, &block)
    else
      key = normalize_key(key)
      unless options && options[:force]
        entry = instrument(:read, key, options) do |payload|
          read_entry(key, options).tap do |result|
            if payload
              payload[:super_operation] = :fetch
              payload[:hit] = !!result
            end
          end
        end
      end

      if entry.nil?
        result = instrument(:generate, key, options) do |payload|
          yield
        end
        write_entry(key, result, options)
        result
      else
        instrument(:fetch_hit, key, options) { |payload| }
        entry
      end
    end
  else
    read(key, options)
  end
end
increment(key, amount = 1, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 154
def increment(key, amount = 1, options = nil)
  key = normalize_key(key)
  instrument(:increment, key, amount: amount) do
    @cache.incr(key, amount)
  end
rescue Memcached::NotFound
  nil
rescue Memcached::Error => e
  log_error(e)
  nil
end
mute() { || ... } click to toggle source

Silence the logger within a block.

# File lib/active_support/cache/libmemcached_store.rb, line 60
def mute
  previous_silence, @silence = defined?(@silence) && @silence, true
  yield
ensure
  @silence = previous_silence
end
read(key, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 117
def read(key, options = nil)
  key = normalize_key(key)
  instrument(:read, key, options) do |payload|
    entry = read_entry(key, options)
    payload[:hit] = !!entry if payload
    entry
  end
end
read_multi(*names) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 178
def read_multi(*names)
  names.flatten!
  options = names.extract_options!

  return {} if names.empty?

  mapping = Hash[names.map {|name| [normalize_key(name), name] }]
  keys = mapping.keys
  raw_values, flags = instrument(:read_multi, keys, options) do
    @cache.get(keys, false, true)
  end

  values = {}
  raw_values.each do |key, value|
    values[mapping[key]] = convert_race_condition_entry(deserialize(value, options[:raw], flags[key]))
  end
  values
rescue Memcached::Error => e
  log_error(e)
  {}
end
silence!() click to toggle source

Silence the logger.

# File lib/active_support/cache/libmemcached_store.rb, line 54
def silence!
  @silence = true
  self
end
stats() click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 206
def stats
  @cache.stats
end
write(key, value, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 126
def write(key, value, options = nil)
  key = normalize_key(key)
  instrument(:write, key, options) do |payload|
    write_entry(key, value, options)
  end
end

Protected Instance Methods

delete_entry(key, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 242
def delete_entry(key, options = nil)
  @cache.delete(key)
  true
rescue Memcached::NotFound
  false
rescue Memcached::Error => e
  log_error(e)
  false
end
read_entry(key, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 212
def read_entry(key, options = nil)
  options ||= {}
  raw_value, flags = @cache.get(key, false, true)
  value = deserialize(raw_value, options[:raw], flags)
  convert_race_condition_entry(value, options)
rescue Memcached::NotFound
  nil
rescue Memcached::Error => e
  log_error(e)
  nil
end
write_entry(key, entry, options = nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 224
def write_entry(key, entry, options = nil)
  options = options ? @options.merge(options) : @options
  method = options[:unless_exist] ? :add : :set
  entry = options[:raw] ? entry.to_s : Marshal.dump(entry)
  flags = 0

  if options[:compress] && entry.bytesize >= options[:compress_threshold]
    entry = Zlib::Deflate.deflate(entry)
    flags |= FLAG_COMPRESSED
  end

  @cache.send(method, key, entry, options[:expires_in].to_i, false, flags)
  true
rescue Memcached::Error => e
  log_error(e)
  false
end

Private Instance Methods

convert_race_condition_entry(value, options={}) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 279
def convert_race_condition_entry(value, options={})
  if !options[:preserve_race_condition_entry] && value.is_a?(FetchWithRaceConditionTTLEntry)
    value.value
  else
    value
  end
end
deserialize(value, raw = false, flags = 0) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 287
def deserialize(value, raw = false, flags = 0)
  value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
  raw ? value : Marshal.load(value)
rescue TypeError, ArgumentError
  value
end
escape_and_normalize(key) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 299
def escape_and_normalize(key)
  key = key.to_s.dup.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
  key_length = key.length

  return key if @namespace_length + key_length <= 250

  max_key_length = 213 - @namespace_length
  "#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
end
fetch_with_race_condition_ttl(key, options={}) { || ... } click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 254
def fetch_with_race_condition_ttl(key, options={}, &block)
  options = options.dup

  race_ttl = options.delete(:race_condition_ttl) || raise("Use :race_condition_ttl option or normal fetch")
  expires_in = options.fetch(:expires_in)
  options[:expires_in] = expires_in + race_ttl
  options[:preserve_race_condition_entry] = true

  value = fetch(key, options) { FetchWithRaceConditionTTLEntry.new(yield, expires_in) }

  return value unless value.is_a?(FetchWithRaceConditionTTLEntry)

  if value.expired? && !value.extended
    # we take care of refreshing the cache, all others should keep reading
    value.extended = true
    write(key, value, options.merge(:expires_in => value.expires_in + race_ttl))

    # calculate new value and store it
    value = FetchWithRaceConditionTTLEntry.new(yield, expires_in)
    write(key, value, options)
  end

  value.value
end
instrument(operation, key, options=nil) { |payload| ... } click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 326
def instrument(operation, key, options=nil)
  log(operation, key, options)

  if instrument?
    payload = { :key => key }
    payload.merge!(options) if options.is_a?(Hash)
    ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
  else
    yield(nil)
  end
end
instrument?() click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 338
def instrument?
  ActiveSupport::VERSION::MAJOR == 3 ? ActiveSupport::Cache::Store.instrument : true
end
log(operation, key, options=nil) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 342
def log(operation, key, options=nil)
  return unless !silence? && logger && logger.debug?
  logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
end
log_error(exception) click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 347
def log_error(exception)
  return unless !silence? && logger && logger.error?
  logger.error "MemcachedError (#{exception.inspect}): #{exception.message}"
end
logger() click to toggle source
# File lib/active_support/cache/libmemcached_store.rb, line 352
def logger
  Rails.logger
end
normalize_key(key, _options = nil) click to toggle source

TODO: support namespace or other things passed as option

# File lib/active_support/cache/libmemcached_store.rb, line 295
def normalize_key(key, _options = nil)
  escape_and_normalize(expanded_key(key))
end