require 'monitor' module IdentityCache class MemoizedCacheProxy attr_accessor :cache_backend def initialize(cache_backend = nil) @cache_backend = cache_backend || Rails.cache @key_value_maps = Hash.new {|h, k| h[k] = {} } end def memoized_key_values @key_value_maps[Thread.current] end def with_memoization(&block) Thread.current[:memoizing_idc] = true yield ensure clear_memoization Thread.current[:memoizing_idc] = false end def write(key, value) memoized_key_values[key] = value if memoizing? @cache_backend.write(key, value) end def read(key) used_cache_backend = true result = if memoizing? used_cache_backend = false mkv = memoized_key_values mkv.fetch(key) do used_cache_backend = true mkv[key] = @cache_backend.read(key) end else @cache_backend.read(key) end if result IdentityCache.logger.debug { "[IdentityCache] #{ used_cache_backend ? '(cache_backend)' : '(memoized)' } cache hit for #{key}" } else IdentityCache.logger.debug { "[IdentityCache] cache miss for #{key}" } end result end def delete(key) memoized_key_values.delete(key) if memoizing? result = @cache_backend.delete(key) IdentityCache.logger.debug { "[IdentityCache] delete #{ result ? 'hit' : 'miss' } for #{key}" } result end def read_multi(*keys) if IdentityCache.logger.debug? memoized_keys , cache_backend_keys = [], [] end result = if memoizing? hash = {} mkv = memoized_key_values missing_keys = keys.reject do |key| if mkv.has_key?(key) memoized_keys << key if IdentityCache.logger.debug? hit = mkv[key] hash[key] = hit unless hit.nil? true end end hits = missing_keys.empty? ? {} : @cache_backend.read_multi(*missing_keys) missing_keys.each do |key| hit = hits[key] mkv[key] = hit hash[key] = hit unless hit.nil? end hash else @cache_backend.read_multi(*keys) end if IdentityCache.logger.debug? result.each do |k, v| cache_backend_keys << k if !v.nil? && !memoized_keys.include?(k) end memoized_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (memoized) cache hit for #{k} (multi)" } cache_backend_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (cache_backend) cache hit for #{k} (multi)" } end result end def clear clear_memoization @cache_backend.clear end private def clear_memoization @key_value_maps.delete(Thread.current) end def memoizing? Thread.current[:memoizing_idc] end end end