Ticket #11540: compilable_thread_safety.patch
| File compilable_thread_safety.patch, 5.2 kB (added by coderrr, 5 months ago) |
|---|
-
actionpack/test/template/threading_test_explicit.rb
old new 1 require 'abstract_unit' 2 3 class ThreadedPartialTemplateTest < Test::Unit::TestCase 4 LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures') 5 ActionView::TemplateFinder.process_view_paths(LOAD_PATH_ROOT) 6 7 # You should let this run for "A Long Time" to be confident there aren't any race conditions 8 # and of course it will never give 100% confidence 9 def test_threading_with_partials_with_different_sets_of_local_assigns 10 orig, Thread.abort_on_exception = Thread.abort_on_exception, true 11 12 template_content = (1..999).inject("") {|s, n| s << %{<%= local_#{n} if defined? local_#{n} %>} } 13 File.open(File.join(LOAD_PATH_ROOT, "test/_threaded_partial.erb"), "w") {|f| f.write template_content } 14 15 # Uncomment to reproduce race condition 16 # ActionView::TemplateHandlers::Compilable.class_eval %{ 17 # @@compile_guard = Object.new 18 # def @@compile_guard.synchronize; yield; end 19 # } 20 21 10000.times do 22 puts "working..." 23 24 ActionView::TemplateHandlers::ERB.template_args.clear 25 ActionView::TemplateHandlers::ERB.compile_time.clear 26 27 threads = [] 28 (1..999).each do |n| 29 threads << Thread.new do 30 view = ActionView::Base.new(LOAD_PATH_ROOT) 31 template = ActionView::PartialTemplate.new(view, 'test/threaded_partial', nil, {"local_#{n}".to_sym => n}) 32 assert_equal "#{n}", template.render 33 end 34 end 35 threads.each {|t| t.join} 36 end 37 ensure 38 Thread.abort_on_exception = orig 39 end 40 end -
actionpack/lib/action_view/template_handlers/compilable.rb
old new 1 1 module ActionView 2 2 module TemplateHandlers 3 3 module Compilable 4 @@compile_guard = Mutex.new 4 5 5 6 def self.included(base) 6 7 base.extend ClassMethod … … 30 31 end 31 32 32 33 # Compile and evaluate the template's code 34 # 35 # RACE CONDITION: If 2+ threads create_template_source for the same method, and the first 36 # thread (whose method has fewer local assigns than the second) module_evals last, it will 37 # clobber the method with the correct set of assigns. The compile_guard mutex prevents this. 33 38 def compile_template(template) 34 39 return unless compile_template?(template) 35 40 36 render_symbol = assign_method_name(template) 37 render_source = create_template_source(template, render_symbol) 38 line_offset = self.template_args[render_symbol].size + self.line_offset 41 @@compile_guard.synchronize do 42 render_symbol = assign_method_name(template) 43 render_source = create_template_source(template, render_symbol) 44 line_offset = self.template_args[render_symbol].size + self.line_offset 39 45 40 begin 41 file_name = template.filename || 'compiled-template' 42 ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset) 43 rescue Exception => e # errors from template code 44 if @view.logger 45 @view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" 46 @view.logger.debug "Function body: #{render_source}" 47 @view.logger.debug "Backtrace: #{e.backtrace.join("\n")}" 46 begin 47 file_name = template.filename || 'compiled-template' 48 ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset) 49 rescue Exception => e # errors from template code 50 if @view.logger 51 @view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" 52 @view.logger.debug "Function body: #{render_source}" 53 @view.logger.debug "Backtrace: #{e.backtrace.join("\n")}" 54 end 55 56 raise ActionView::TemplateError.new(template, @view.assigns, e) 48 57 end 49 58 50 raise ActionView::TemplateError.new(template, @view.assigns, e) 59 self.compile_time[render_symbol] = Time.now 60 # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger 51 61 end 52 53 self.compile_time[render_symbol] = Time.now54 # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger55 62 end 56 63 57 64 private … … 99 106 100 107 self.template_args[render_symbol] ||= {} 101 108 locals_keys = self.template_args[render_symbol].keys | template.locals.keys 109 # RACE CONDITION: If 2+ threads execute the line above before executing the line below, 110 # one of their local_keys may be missing keys. The compile_guard mutex prevents this. 102 111 self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } 103 112 104 113 locals_code = "" … … 125 134 126 135 end 127 136 end 128 end 137 end