Skip to content

Fix duplicate view template loading #51712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

nhasselmeyer
Copy link

Motivation / Background

Currently, when rendering an ActionView::Template, the template source is loaded from the disk twice.

During one rendering process, Template#source is called at least twice (once in encode! and once in strict_locals!), each of these calls runs @source.to_s. As @source is usually a Template::Sources::File, #to_s hits the disk to load the template.

It is unnecessary to load the file from disk more than once just to check whether the # locals: comment is present.

Detail

This Pull Request changes the Template#source so that it memoizes the loaded source.

Additional information

This pull request introduces a small behavior change: The # locals: (...) comment is now removed from the source before passing the source to the handler. It looks like this was the intended behavior all along. Previously the line

self.source.sub!(STRICT_LOCALS_REGEX, "")

had no effect, because the source was reloaded from disk afterwards and the magic comment was present again.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@rails-bot rails-bot bot added the actionview label May 2, 2024
@nhasselmeyer nhasselmeyer force-pushed the nha/fix-duplicate-template-loading branch from 97ec459 to 2ed2f23 Compare May 2, 2024 12:44
@willianveiga
Copy link
Contributor

The tests are passing with the following alternative implementation:

diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 9b7dfc2b56..6d48c55ba3 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -186,26 +186,26 @@ def mime_types_implementation=(implementation)
         # This method isn't thread-safe, but it's not supposed
         # to be called after initialization
         if self::Types != implementation
           remove_const(:Types)
           const_set(:Types, implementation)
         end
       end
     end
 
     attr_reader :identifier, :handler
-    attr_reader :variable, :format, :variant, :virtual_path
+    attr_reader :variable, :source, :format, :variant, :virtual_path
 
     NONE = Object.new
 
     def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
-      @source            = source.dup
+      @source            = source.to_s.dup
       @identifier        = identifier
       @handler           = handler
       @compiled          = false
       @locals            = locals
       @virtual_path      = virtual_path
 
       @variable = if @virtual_path
         base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
         base =~ /\A_?(.*?)(?:\.\w+)*\z/
         $1.to_sym
@@ -285,24 +285,20 @@ def type
     end
 
     def short_identifier
       @short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier
     end
 
     def inspect
       "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>"
     end
 
-    def source
-      @source.to_s
-    end
-
     LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/
     private_constant :LEADING_ENCODING_REGEXP
 
     # This method is responsible for properly setting the encoding of the
     # source. Until this point, we assume that the source is BINARY data.
     # If no additional information is supplied, we assume the encoding is
     # the same as <tt>Encoding.default_external</tt>.
     #
     # The user can also specify the encoding via a comment on the first
     # line of the template (<tt># encoding: NAME-OF-ENCODING</tt>). This will work

Does it make sense, @nhasselmeyer? Thanks.

@nhasselmeyer
Copy link
Author

nhasselmeyer commented Jun 3, 2024

@willianveiga I thought about implementing that, too, but there is a problem: Not every template that is initialized will need the loaded source.

If, for example, there exists a foo.html.erb and a foo+mobile.html.erb, then a

render 'foo', variants: [:mobile]`

will initialize a ActionView::Template for both of them. Later code will select the mobile variant and render only that. But with your implementation, the source for the template without a variant would be loaded, too, negating the performance win.

Copy link
Member

@skipkayhil skipkayhil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good change since we call template.source other places, like 2-3 times for the same template in the RubyTracker:

def render_dependencies
return [] unless template.source.include?("render")
compiled_source = template.handler.call(template, template.source)
@parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call|
render_call.gsub(%r|/_|, "/")
end
end
def explicit_dependencies
template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
end

I left some suggestions for variable names, but other than that I'll try to get this merged 👍

@nhasselmeyer nhasselmeyer force-pushed the nha/fix-duplicate-template-loading branch from 2ed2f23 to 3330a17 Compare October 8, 2024 08:55
@nhasselmeyer
Copy link
Author

@skipkayhil Thank you for your suggestions! I edited them in and rebased the branch.

@nhasselmeyer nhasselmeyer force-pushed the nha/fix-duplicate-template-loading branch from 3330a17 to 564fab1 Compare October 8, 2024 17:14
Currently, when rendering an ActionView::Template, the template source
is loaded from the disk twice.

During one rendering process, Template#source is called at least twice
(once in encode! and once in strict_locals!), each of these calls runs
`@source.to_s`. As `@source` is usually a Template::Sources::File, #to_s
hits the disk to load the template.

It is unnecessary to load the file from disk more than once just to
check whether the "# locals:" comment is present.
@skipkayhil skipkayhil force-pushed the nha/fix-duplicate-template-loading branch from 564fab1 to b42e9aa Compare October 10, 2024 18:49
@skipkayhil
Copy link
Member

I spoke to John about this PR and he said we shouldn't be memoizing the rendered source. After thinking about it more, this makes sense because it would greatly increase memory usage if we keep the rendered source around forever (we only need it to compile the view method and maybe calculate render dependencies and then not really after that).

Since the current approach isn't going to work, I'm going to have to close this PR. I appreciate your willingness to stick with it for so long and I'm sorry it took so long to get feedback. Thank you for contributing and I hope to see more contributions in the future 🙂

@skipkayhil skipkayhil closed this Oct 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants