Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #11540: compilable_thread_safety.2.patch

File compilable_thread_safety.2.patch, 5.2 kB (added by coderrr, 3 months ago)
  • actionpack/test/template/threading_test_explicit.rb

    old new  
     1require 'abstract_unit' 
     2 
     3class 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 
     40end 
  • actionpack/lib/action_view/template_handlers/compilable.rb

    old new  
    11module ActionView 
    22  module TemplateHandlers 
    33    module Compilable 
     4      @@compile_guard = Mutex.new 
    45 
    56      def self.included(base) 
    67        base.extend ClassMethod 
     
    3031      end 
    3132 
    3233      # 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. 
    3338      def compile_template(template) 
    3439        return unless compile_template?(template) 
    3540 
    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 
    3945 
    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) 
    4857          end 
    4958 
    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 
    5161        end 
    52  
    53         self.compile_time[render_symbol] = Time.now 
    54         # logger.debug "Compiled template #{file_name || template}\n  ==> #{render_symbol}" if logger 
    5562      end 
    5663 
    5764      private 
     
    99106 
    100107        self.template_args[render_symbol] ||= {} 
    101108        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. 
    102111        self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } 
    103112 
    104113        locals_code = "" 
     
    125134 
    126135    end 
    127136  end 
    128 end 
     137end