From ee51b51b60f9e6cce9babed2c8a65a14d87790c8 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Wed, 21 Apr 2010 23:22:05 -0500 Subject: ActiveSupport::Cache refactoring All Caches * Add default options to initializer that will be sent to all read, write, fetch, exist?, increment, and decrement * Add support for the :expires_in option to fetch and write for all caches. Cache entries are stored with the create timestamp and a ttl so that expiration can be handled independently of the implementation. * Add support for a :namespace option. This can be used to set a global prefix for cache entries. * Deprecate expand_cache_key on ActiveSupport::Cache and move it to ActionController::Caching and ActionDispatch::Http::Cache since the logic in the method used some Rails specific environment variables and was only used by ActionPack classes. Not very DRY but there didn't seem to be a good shared spot and ActiveSupport really shouldn't be Rails specific. * Add support for :race_condition_ttl to fetch. This setting can prevent race conditions on fetch calls where several processes try to regenerate a recently expired entry at once. * Add support for :compress option to fetch and write which will compress any data over a configurable threshold. * Nil values can now be stored in the cache and are distinct from cache misses for fetch. * Easier API to create new implementations. Just need to implement the methods read_entry, write_entry, and delete_entry instead of overwriting existing methods. * Since all cache implementations support storing objects, update the docs to state that ActiveCache::Cache::Store implementations should store objects. Keys, however, must be strings since some implementations require that. * Increase test coverage. * Document methods which are provided as convenience but which may not be universally available. MemoryStore * MemoryStore can now safely be used as the cache for single server sites. * Make thread safe so that the default cache implementation used by Rails is thread safe. The overhead is minimal and it is still the fastest store available. * Provide :size initialization option indicating the maximum size of the cache in memory (defaults to 32Mb). * Add prune logic that removes the least recently used cache entries to keep the cache size from exceeding the max. * Deprecated SynchronizedMemoryStore since it isn't needed anymore. FileStore * Escape key values so they will work as file names on all file systems, be consistent, and case sensitive * Use a hash algorithm to segment the cache into sub directories so that a large cache doesn't exceed file system limits. * FileStore can be slow so implement the LocalCache strategy to cache reads for the duration of a request. * Add cleanup method to keep the disk from filling up with expired entries. * Fix increment and decrement to use file system locks so they are consistent between processes. MemCacheStore * Support all keys. Previously keys with spaces in them would fail * Deprecate CompressedMemCacheStore since it isn't needed anymore (use :compress => true) [#4452 state:committed] Signed-off-by: Jeremy Kemper --- .../active_support/cache/strategy/local_cache.rb | 154 ++++++++++++++------- 1 file changed, 102 insertions(+), 52 deletions(-) (limited to 'activesupport/lib/active_support/cache/strategy') diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index bbbd643736..8942587ac8 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -4,17 +4,54 @@ require 'active_support/core_ext/string/inflections' module ActiveSupport module Cache module Strategy + # Caches that implement LocalCache will be backed by an in memory cache for the + # duration of a block. Repeated calls to the cache for the same key will hit the + # in memory cache for faster access. module LocalCache - # this allows caching of the fact that there is nothing in the remote cache - NULL = 'remote_cache_store:null' + # Simple memory backed cache. This cache is not thread safe but is intended only + # for serving as a temporary memory cache for a single thread. + class LocalStore < Store + def initialize + super + @data = {} + end + + # Since it isn't thread safe, don't allow synchronizing. + def synchronize # :nodoc: + yield + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key, options) + @data[key] + end + def write_entry(key, value, options) + @data[key] = value + true + end + + def delete_entry(key, options) + !!@data.delete(key) + end + end + + # Use a local cache to front for the cache for the duration of a block. def with_local_cache - Thread.current[thread_local_key] = MemoryStore.new - yield - ensure - Thread.current[thread_local_key] = nil + save_val = Thread.current[thread_local_key] + begin + Thread.current[thread_local_key] = LocalStore.new + yield + ensure + Thread.current[thread_local_key] = save_val + end end + # Middleware class can be inserted as a Rack handler to use a local cache for the + # duration of a request. def middleware @middleware ||= begin klass = Class.new @@ -24,7 +61,7 @@ module ActiveSupport end def call(env) - Thread.current[:#{thread_local_key}] = MemoryStore.new + Thread.current[:#{thread_local_key}] = LocalStore.new @app.call(env) ensure Thread.current[:#{thread_local_key}] = nil @@ -39,73 +76,86 @@ module ActiveSupport end end - def read(key, options = nil) - value = local_cache && local_cache.read(key) - if value == NULL - nil - elsif value.nil? - value = super - local_cache.mute { local_cache.write(key, value || NULL) } if local_cache - value.duplicable? ? value.dup : value - else - # forcing the value to be immutable - value.duplicable? ? value.dup : value - end - end - - def write(key, value, options = nil) - value = value.to_s if respond_to?(:raw?) && raw?(options) - local_cache.mute { local_cache.write(key, value || NULL) } if local_cache + def clear(options = nil) # :nodoc: + local_cache.clear(options) if local_cache super end - def delete(key, options = nil) - local_cache.mute { local_cache.write(key, NULL) } if local_cache + def cleanup(options = nil) # :nodoc: + local_cache.clear(options) if local_cache super end - def exist(key, options = nil) - value = local_cache.read(key) if local_cache - if value == NULL - false - elsif value - true - else - super + def increment(name, amount = 1, options = nil) # :nodoc: + value = bypass_local_cache{super} + if local_cache + local_cache.mute do + if value + local_cache.write(name, value, options) + else + local_cache.delete(name, options) + end + end end + value end - def increment(key, amount = 1) - if value = super - local_cache.mute { local_cache.write(key, value.to_s) } if local_cache - value - else - nil + def decrement(name, amount = 1, options = nil) # :nodoc: + value = bypass_local_cache{super} + if local_cache + local_cache.mute do + if value + local_cache.write(name, value, options) + else + local_cache.delete(name, options) + end + end end + value end - def decrement(key, amount = 1) - if value = super - local_cache.mute { local_cache.write(key, value.to_s) } if local_cache - value - else - nil + protected + def read_entry(key, options) # :nodoc: + if local_cache + entry = local_cache.read_entry(key, options) + unless entry + entry = super + local_cache.write_entry(key, entry, options) + end + entry + else + super + end end - end - def clear - local_cache.clear if local_cache - super - end + def write_entry(key, entry, options) # :nodoc: + local_cache.write_entry(key, entry, options) if local_cache + super + end + + def delete_entry(key, options) # :nodoc: + local_cache.delete_entry(key, options) if local_cache + super + end private def thread_local_key - @thread_local_key ||= "#{self.class.name.underscore}_local_cache".gsub("/", "_").to_sym + @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{self.object_id}".gsub("/", "_").to_sym end def local_cache Thread.current[thread_local_key] end + + def bypass_local_cache + save_cache = Thread.current[thread_local_key] + begin + Thread.current[thread_local_key] = nil + yield + ensure + Thread.current[thread_local_key] = save_cache + end + end end end end -- cgit v1.2.3