diff options
Diffstat (limited to 'actionview/lib/action_view/digestor.rb')
-rw-r--r-- | actionview/lib/action_view/digestor.rb | 153 |
1 files changed, 71 insertions, 82 deletions
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 6f2f9ca53c..b91e61da18 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -4,16 +4,7 @@ require 'monitor' module ActionView class Digestor - cattr_reader(:cache) - @@cache = Concurrent::Map.new - @@digest_monitor = Monitor.new - - class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc: - def call(env) - ActionView::Digestor.cache.clear - app.call(env) - end - end + @@digest_mutex = Mutex.new class << self # Supported options: @@ -22,106 +13,104 @@ module ActionView # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views # * <tt>partial</tt> - Specifies whether the template is a partial - def digest(options) - options.assert_valid_keys(:name, :finder, :dependencies, :partial) - - cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') + def digest(name:, finder:, dependencies: []) + dependencies ||= [] + cache_key = ([ name ].compact + dependencies).join('.') # this is a correctly done double-checked locking idiom # (Concurrent::Map's lookups have volatile semantics) - @@cache[cache_key] || @@digest_monitor.synchronize do - @@cache.fetch(cache_key) do # re-check under lock - compute_and_store_digest(cache_key, options) + finder.digest_cache[cache_key] || @@digest_mutex.synchronize do + finder.digest_cache.fetch(cache_key) do # re-check under lock + partial = name.include?("/_") + root = tree(name, finder, partial) + dependencies.each do |injected_dep| + root.children << Injected.new(injected_dep, nil, nil) + end + finder.digest_cache[cache_key] = root.digest(finder) end end end - private - def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock - klass = if options[:partial] || options[:name].include?("/_") - # Prevent re-entry or else recursive templates will blow the stack. - # There is no need to worry about other threads seeing the +false+ value, - # as they will then have to wait for this thread to let go of the @@digest_monitor lock. - pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion - PartialDigestor - else - Digestor - end + def logger + ActionView::Base.logger || NullLogger + end - @@cache[cache_key] = stored_digest = klass.new(options).digest - ensure - # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest - end - end + # Create a dependency tree for template named +name+. + def tree(name, finder, partial = false, seen = {}) + logical_name = name.gsub(%r|/_|, "/") - attr_reader :name, :finder, :options + if finder.disable_cache { finder.exists?(logical_name, [], partial) } + template = finder.disable_cache { finder.find(logical_name, [], partial) } - def initialize(options) - @name, @finder = options.values_at(:name, :finder) - @options = options.except(:name, :finder) - end + if node = seen[template.identifier] # handle cycles in the tree + node + else + node = seen[template.identifier] = Node.create(name, logical_name, template, partial) - def digest - Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest| - logger.try :debug, " Cache digest for #{template.inspect}: #{digest}" + deps = DependencyTracker.find_dependencies(name, template, finder.view_paths) + deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file| + node.children << tree(dep_file, finder, true, seen) + end + node + end + else + logger.error " '#{name}' file doesn't exist, so no dependencies" + logger.error " Couldn't find template for digesting: #{name}" + seen[name] ||= Missing.new(name, logical_name, nil) + end end - rescue ActionView::MissingTemplate - logger.try :error, " Couldn't find template for digesting: #{name}" - '' end - def dependencies - DependencyTracker.find_dependencies(name, template, finder.view_paths) - rescue ActionView::MissingTemplate - logger.try :error, " '#{name}' file doesn't exist, so no dependencies" - [] - end + class Node + attr_reader :name, :logical_name, :template, :children - def nested_dependencies - dependencies.collect do |dependency| - dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies - dependencies.any? ? { dependency => dependencies } : dependency + def self.create(name, logical_name, template, partial) + klass = partial ? Partial : Node + klass.new(name, logical_name, template, []) end - end - private - def logger - ActionView::Base.logger + def initialize(name, logical_name, template, children = []) + @name = name + @logical_name = logical_name + @template = template + @children = children end - def logical_name - name.gsub(%r|/_|, "/") + def digest(finder, stack = []) + Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") end - def partial? - false + def dependency_digest(finder, stack) + children.map do |node| + if stack.include?(node) + false + else + finder.digest_cache[node.name] ||= begin + stack.push node + node.digest(finder, stack).tap { stack.pop } + end + end + end.join("-") end - def template - @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) } + def to_dep_map + children.any? ? { name => children.map(&:to_dep_map) } : name end + end - def source - template.source - end + class Partial < Node; end - def dependency_digest - template_digests = dependencies.collect do |template_name| - Digestor.digest(name: template_name, finder: finder, partial: true) - end - - (template_digests + injected_dependencies).join("-") - end + class Missing < Node + def digest(finder, _ = []) '' end + end - def injected_dependencies - Array.wrap(options[:dependencies]) - end - end + class Injected < Node + def digest(finder, _ = []) name end + end - class PartialDigestor < Digestor # :nodoc: - def partial? - true + class NullLogger + def self.debug(_); end + def self.error(_); end end end end |