aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template/resolver.rb
diff options
context:
space:
mode:
authorTom Clarke <tom@u2i.com>2012-05-20 01:03:34 -0400
committerTom Clarke <tom@u2i.com>2012-05-21 15:24:07 -0400
commit2a121870a6aba2b40d65e75d8fa4f7b959aac2c9 (patch)
treeed462901756df33b9b24eccbbcec69091c89c52b /actionpack/lib/action_view/template/resolver.rb
parent776ea1090fb9ba4353035d4983cf1926a67b3294 (diff)
downloadrails-2a121870a6aba2b40d65e75d8fa4f7b959aac2c9.tar.gz
rails-2a121870a6aba2b40d65e75d8fa4f7b959aac2c9.tar.bz2
rails-2a121870a6aba2b40d65e75d8fa4f7b959aac2c9.zip
Make the Resolver template cache threadsafe - closes #6404
The Template cache in the Resolver can be accessed by multiple threads similtaneously in multi-threaded environments. The cache is implemented using a Hash, which isn't threadsafe in all VMs (notably JRuby). This commit extracts the cache to a new Cache class and adds mutexes to prevent concurrent access.
Diffstat (limited to 'actionpack/lib/action_view/template/resolver.rb')
-rw-r--r--actionpack/lib/action_view/template/resolver.rb69
1 files changed, 50 insertions, 19 deletions
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index fa2038f78d..b33aebd3bc 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -2,6 +2,7 @@ require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
+require "thread"
module ActionView
# = Action View Resolver
@@ -24,6 +25,46 @@ module ActionView
end
end
+ # Threadsafe template cache
+ class Cache #:nodoc:
+ def initialize
+ @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
+ h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @mutex = Mutex.new
+ end
+
+ # Cache the templates returned by the block
+ def cache(key, name, prefix, partial, locals)
+ @mutex.synchronize do
+ if Resolver.caching?
+ # all templates are cached forever the first time they are accessed
+ @data[key][name][prefix][partial][locals] ||= yield
+ else
+ # templates are still cached, but are only returned if they are
+ # all still current
+ fresh = yield
+
+ cache = @data[key][name][prefix][partial][locals]
+ mtime = cache && cache.map(&:updated_at).max
+
+ newer = !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
+
+ if newer
+ @data[key][name][prefix][partial][locals] = fresh
+ else
+ @data[key][name][prefix][partial][locals]
+ end
+ end
+ end
+ end
+
+ def clear
+ @mutex.synchronize do
+ @data.clear
+ end
+ end
+ end
+
cattr_accessor :caching
self.caching = true
@@ -32,12 +73,11 @@ module ActionView
end
def initialize
- @cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
- h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @cache = Cache.new
end
def clear_cache
- @cached.clear
+ @cache.clear
end
# Normalizes the arguments and passes it on to find_template.
@@ -65,27 +105,18 @@ module ActionView
# Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
- # it always hits the resolver but check if the resolver is fresher
- # before returning it.
+ # it always hits the resolver but if the key is present, check if the
+ # resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
locals = locals.map { |x| x.to_s }.sort!
- if key && caching?
- @cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
- else
- fresh = decorate(yield, path_info, details, locals)
- return fresh unless key
-
- scope = @cached[key][name][prefix][partial]
- cache = scope[locals]
- mtime = cache && cache.map(&:updated_at).max
-
- if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
- scope[locals] = fresh
- else
- cache
+ if key
+ @cache.cache(key, name, prefix, partial, locals) do
+ decorate(yield, path_info, details, locals)
end
+ else
+ decorate(yield, path_info, details, locals)
end
end