Skip to content

Commit 2daef10

Browse files
authored
Merge pull request rails#42945 from jhawthorn/hash_match
Remove details_key-based Template cache
2 parents 891189d + bb52086 commit 2daef10

File tree

5 files changed

+27
-123
lines changed

5 files changed

+27
-123
lines changed

actionview/lib/action_view/lookup_context.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def self.details_cache_key(details)
6767
details = details.dup
6868
details[:formats] &= Template::Types.symbols
6969
end
70-
@details_keys[details] ||= Object.new
70+
@details_keys[details] ||= TemplateDetails::Requested.new(**details)
7171
end
7272

7373
def self.clear

actionview/lib/action_view/template/resolver.rb

Lines changed: 5 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -49,84 +49,18 @@ def parse(path)
4949
end
5050
end
5151

52-
# Threadsafe template cache
53-
class Cache # :nodoc:
54-
class SmallCache < Concurrent::Map
55-
def initialize(options = {})
56-
super(options.merge(initial_capacity: 2))
57-
end
58-
end
59-
60-
# Preallocate all the default blocks for performance/memory consumption reasons
61-
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
62-
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
63-
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
64-
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
65-
66-
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
67-
NO_TEMPLATES = [].freeze
68-
69-
def initialize
70-
@data = SmallCache.new(&KEY_BLOCK)
71-
end
72-
73-
def inspect
74-
"#{to_s[0..-2]} keys=#{@data.size}>"
75-
end
76-
77-
# Cache the templates returned by the block
78-
def cache(key, name, prefix, partial, locals)
79-
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
80-
end
81-
82-
def clear
83-
@data.clear
84-
end
85-
86-
# Get the cache size. Do not call this
87-
# method. This method is not guaranteed to be here ever.
88-
def size # :nodoc:
89-
size = 0
90-
@data.each_value do |v1|
91-
v1.each_value do |v2|
92-
v2.each_value do |v3|
93-
v3.each_value do |v4|
94-
size += v4.size
95-
end
96-
end
97-
end
98-
end
99-
100-
size
101-
end
102-
103-
private
104-
def canonical_no_templates(templates)
105-
templates.empty? ? NO_TEMPLATES : templates
106-
end
107-
end
108-
10952
cattr_accessor :caching, default: true
11053

11154
class << self
11255
alias :caching? :caching
11356
end
11457

115-
def initialize
116-
@cache = Cache.new
117-
end
118-
11958
def clear_cache
120-
@cache.clear
12159
end
12260

12361
# Normalizes the arguments and passes it on to find_templates.
12462
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
125-
locals = locals.map(&:to_s).sort!.freeze
126-
127-
cached(key, [name, prefix, partial], details, locals) do
128-
_find_all(name, prefix, partial, details, key, locals)
129-
end
63+
_find_all(name, prefix, partial, details, key, locals)
13064
end
13165

13266
def all_template_paths # :nodoc:
@@ -147,22 +81,6 @@ def _find_all(name, prefix, partial, details, key, locals)
14781
def find_templates(name, prefix, partial, details, locals = [])
14882
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
14983
end
150-
151-
# Handles templates caching. If a key is given and caching is on
152-
# always check the cache before hitting the resolver. Otherwise,
153-
# it always hits the resolver but if the key is present, check if the
154-
# resolver is fresher before returning it.
155-
def cached(key, path_info, details, locals)
156-
name, prefix, partial = path_info
157-
158-
if key
159-
@cache.cache(key, name, prefix, partial, locals) do
160-
yield
161-
end
162-
else
163-
yield
164-
end
165-
end
16684
end
16785

16886
# A resolver that loads files from the filesystem.
@@ -204,16 +122,12 @@ def all_template_paths # :nodoc:
204122

205123
private
206124
def _find_all(name, prefix, partial, details, key, locals)
207-
path = TemplatePath.build(name, prefix, partial)
208-
requested_details = TemplateDetails::Requested.new(**details)
209-
query(path, requested_details, locals, cache: !!key)
210-
end
211-
212-
def query(path, requested_details, locals, cache:)
213-
cache = cache ? @unbound_templates : Concurrent::Map.new
125+
requested_details = key || TemplateDetails::Requested.new(**details)
126+
cache = key ? @unbound_templates : Concurrent::Map.new
214127

215128
unbound_templates =
216-
cache.compute_if_absent(path.virtual) do
129+
cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
130+
path = TemplatePath.build(name, prefix, partial)
217131
unbound_templates_from_path(path)
218132
end
219133

actionview/lib/action_view/unbound_template.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,25 @@ def initialize(source, identifier, details:, virtual_path:)
1414
@virtual_path = virtual_path
1515

1616
@templates = Concurrent::Map.new(initial_capacity: 2)
17+
@write_lock = Mutex.new
1718
end
1819

1920
def bind_locals(locals)
20-
@templates[locals] ||= build_template(locals)
21+
if template = @templates[locals]
22+
template
23+
else
24+
@write_lock.synchronize do
25+
normalized_locals = normalize_locals(locals)
26+
27+
# We need ||=, both to dedup on the normalized locals and to check
28+
# while holding the lock.
29+
@templates[normalized_locals] ||= build_template(normalized_locals)
30+
31+
# This may have already been assigned, but we've already de-dup'd so
32+
# reassignment is fine.
33+
@templates[locals.dup] = @templates[normalized_locals]
34+
end
35+
end
2136
end
2237

2338
private
@@ -31,8 +46,12 @@ def build_template(locals)
3146
variant: variant&.to_s,
3247
virtual_path: @virtual_path,
3348

34-
locals: locals
49+
locals: locals.map(&:to_s)
3550
)
3651
end
52+
53+
def normalize_locals(locals)
54+
locals.map(&:to_sym).sort!.freeze
55+
end
3756
end
3857
end

actionview/test/actionpack/abstract/layouts_test.rb

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -285,25 +285,6 @@ class TestBase < ActiveSupport::TestCase
285285
assert_equal "With String hello less than 3 bar", controller.response_body
286286
end
287287

288-
test "cache should not grow when locals change for a string template" do
289-
cache = WithString.view_paths.paths.first.instance_variable_get(:@cache)
290-
291-
controller = WithString.new
292-
controller.process(:index) # heat the cache
293-
294-
size = cache.size
295-
296-
10.times do |x|
297-
controller = WithString.new
298-
controller.define_singleton_method :index do
299-
render template: ActionView::Template::Text.new("Hello string!"), locals: { "x#{x}": :omg }
300-
end
301-
controller.process(:index)
302-
end
303-
304-
assert_equal size, cache.size
305-
end
306-
307288
test "when layout is specified as a string, render with that layout" do
308289
controller = WithString.new
309290
controller.process(:index)

actionview/test/template/resolver_cache_test.rb

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)