# frozen_string_literal: truebeginrequire"dalli"rescueLoadError=>e$stderr.puts"You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"raiseeendrequire"active_support/core_ext/marshal"require"active_support/core_ext/array/extract_options"moduleActiveSupportmoduleCache# A cache store implementation which stores data in Memcached:# https://memcached.org## This is currently the most popular cache store for production websites.## Special features:# - Clustering and load balancing. One can specify multiple memcached servers,# and MemCacheStore will load balance between all available servers. If a# server goes down, then MemCacheStore will ignore it until it comes back up.## MemCacheStore implements the Strategy::LocalCache strategy which implements# an in-memory cache inside of a block.classMemCacheStore<Store# Provide support for raw values in the local cache strategy.moduleLocalCacheWithRaw# :nodoc:privatedefread_entry(key,options)entry=superifoptions[:raw]&&local_cache&&entryentry=deserialize_entry(entry.value)endentryenddefwrite_entry(key,entry,options)ifoptions[:raw]&&local_cacheraw_entry=Entry.new(entry.value.to_s)raw_entry.expires_at=entry.expires_atsuper(key,raw_entry,options)elsesuperendendend# Advertise cache versioning support.defself.supports_cache_versioning?trueendprependStrategy::LocalCacheprependLocalCacheWithRawESCAPE_KEY_CHARS=/[\x00-\x20%\x7F-\xFF]/n# Creates a new Dalli::Client instance with specified addresses and options.# By default address is equal localhost:11211.## ActiveSupport::Cache::MemCacheStore.build_mem_cache# # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil># ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>defself.build_mem_cache(*addresses)# :nodoc:addresses=addresses.flattenoptions=addresses.extract_options!addresses=["localhost:11211"]ifaddresses.empty?pool_options=retrieve_pool_options(options)ifpool_options.empty?Dalli::Client.new(addresses,options)elseensure_connection_pool_added!ConnectionPool.new(pool_options){Dalli::Client.new(addresses,options.merge(threadsafe: false))}endend# Creates a new MemCacheStore object, with the given memcached server# addresses. Each address is either a host name, or a host-with-port string# in the form of "host_name:port". For example:## ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")## If no addresses are specified, then MemCacheStore will connect to# localhost port 11211 (the default memcached port).definitialize(*addresses)addresses=addresses.flattenoptions=addresses.extract_options!super(options)unless[String,Dalli::Client,NilClass].include?(addresses.first.class)raiseArgumentError,"First argument must be an empty array, an array of hosts or a Dalli::Client instance."endifaddresses.first.is_a?(Dalli::Client)@data=addresses.firstelsemem_cache_options=options.dupUNIVERSAL_OPTIONS.each{|name|mem_cache_options.delete(name)}@data=self.class.build_mem_cache(*(addresses+[mem_cache_options]))endend# Increment a cached value. This method uses the memcached incr atomic# operator and can only be used on values written with the :raw option.# Calling it on a value not stored with :raw will initialize that value# to zero.defincrement(name,amount=1,options=nil)options=merged_options(options)instrument(:increment,name,amount: amount)dorescue_error_withnildo@data.with{|c|c.incr(normalize_key(name,options),amount,options[:expires_in])}endendend# Decrement a cached value. This method uses the memcached decr atomic# operator and can only be used on values written with the :raw option.# Calling it on a value not stored with :raw will initialize that value# to zero.defdecrement(name,amount=1,options=nil)options=merged_options(options)instrument(:decrement,name,amount: amount)dorescue_error_withnildo@data.with{|c|c.decr(normalize_key(name,options),amount,options[:expires_in])}endendend# Clear the entire cache on all memcached servers. This method should# be used with care when shared cache is being used.defclear(options=nil)rescue_error_with(nil){@data.with{|c|c.flush_all}}end# Get the statistics from the memcached servers.defstats@data.with{|c|c.stats}endprivate# Read an entry from the cache.defread_entry(key,options)rescue_error_with(nil){deserialize_entry(@data.with{|c|c.get(key,options)})}end# Write an entry to the cache.defwrite_entry(key,entry,options)method=options&&options[:unless_exist]?:add::setvalue=options[:raw]?entry.value.to_s:entryexpires_in=options[:expires_in].to_iifexpires_in>0&&!options[:raw]# Set the memcache expire a few minutes in the future to support race condition ttls on readexpires_in+=5.minutesendrescue_error_withfalsedo@data.with{|c|c.send(method,key,value,expires_in,options)}endend# Reads multiple entries from the cache implementation.defread_multi_entries(names,options)keys_to_names=Hash[names.map{|name|[normalize_key(name,options),name]}]raw_values=@data.with{|c|c.get_multi(keys_to_names.keys)}values={}raw_values.eachdo|key,value|entry=deserialize_entry(value)unlessentry.expired?||entry.mismatched?(normalize_version(keys_to_names[key],options))values[keys_to_names[key]]=entry.valueendendvaluesend# Delete an entry from the cache.defdelete_entry(key,options)rescue_error_with(false){@data.with{|c|c.delete(key)}}end# Memcache keys are binaries. So we need to force their encoding to binary# before applying the regular expression to ensure we are escaping all# characters properly.defnormalize_key(key,options)key=super.dupkey=key.force_encoding(Encoding::ASCII_8BIT)key=key.gsub(ESCAPE_KEY_CHARS){|match|"%#{match.getbyte(0).to_s(16).upcase}"}key="#{key[0,213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}"ifkey.size>250keyenddefdeserialize_entry(raw_value)ifraw_valueentry=Marshal.load(raw_value)rescueraw_valueentry.is_a?(Entry)?entry:Entry.new(entry)endenddefrescue_error_with(fallback)yieldrescueDalli::DalliError=>elogger.error("DalliError (#{e}): #{e.message}")ifloggerfallbackendendendend