Ticket #10803: rails-threading.patch
| File rails-threading.patch, 17.3 kB (added by wesley.moxam, 9 months ago) |
|---|
-
activesupport/test/dependencies/my_model.rb
old new 1 class MyModel < ActiveRecord::Base 2 sleep 0.5 3 def self.my_number 4 5 5 end 6 end -
activesupport/test/dependencies_test.rb
old new 30 30 Dependencies.explicitly_unloadable_constants = [] 31 31 end 32 32 33 def test_concurrent_load_or_require 34 assert_nothing_raised do 35 with_loading 'autoloading_fixtures' do 36 threads = [] 37 5.times { |i| 38 threads[i] = Thread.new { 39 require_dependency(File.dirname(__FILE__) + "/dependencies/my_model") 40 assert MyModel.my_number == 5 41 } 42 } 43 threads.each &:join 44 end 45 end 46 assert_equal 1, Dependencies.loaded.size 47 end 48 33 49 def test_tracking_loaded_files 34 50 require_dependency 'dependencies/service_one' 35 51 require_dependency 'dependencies/service_two' -
activesupport/lib/active_support/dependencies.rb
old new 50 50 # An internal stack used to record which constants are loaded by any block. 51 51 mattr_accessor :constant_watch_stack 52 52 self.constant_watch_stack = [] 53 53 54 54 def load? 55 55 mechanism == :load 56 56 end … … 73 73 end 74 74 75 75 def require_or_load(file_name, const_path = nil) 76 log_call file_name, const_path 77 file_name = $1 if file_name =~ /^(.*)\.rb$/ 78 expanded = File.expand_path(file_name) 79 return if loaded.include?(expanded) 76 Thread.exclusive { 77 log_call file_name, const_path 78 file_name = $1 if file_name =~ /^(.*)\.rb$/ 79 expanded = File.expand_path(file_name) 80 return if loaded.include?(expanded) 80 81 81 # Record that we've seen this file *before* loading it to avoid an82 # infinite loop with mutual dependencies.83 loaded << expanded82 # Record that we've seen this file *before* loading it to avoid an 83 # infinite loop with mutual dependencies. 84 loaded << expanded 84 85 85 if load?86 log "loading #{file_name}"87 begin88 # Enable warnings iff this file has not been loaded before and89 # warnings_on_first_load is set.90 load_args = ["#{file_name}.rb"]91 load_args << const_path unless const_path.nil?86 if load? 87 log "loading #{file_name}" 88 begin 89 # Enable warnings iff this file has not been loaded before and 90 # warnings_on_first_load is set. 91 load_args = ["#{file_name}.rb"] 92 load_args << const_path unless const_path.nil? 92 93 93 if !warnings_on_first_load or history.include?(expanded) 94 result = load_file(*load_args) 95 else 96 enable_warnings { result = load_file(*load_args) } 94 if !warnings_on_first_load or history.include?(expanded) 95 result = load_file(*load_args) 96 else 97 enable_warnings { result = load_file(*load_args) } 98 end 99 rescue Exception 100 loaded.delete expanded 101 raise 97 102 end 98 rescue Exception99 lo aded.delete expanded100 r aise103 else 104 log "requiring #{file_name}" 105 result = require file_name 101 106 end 102 else103 log "requiring #{file_name}"104 result = require file_name105 end106 107 107 # Record history *after* loading so first load gets warnings. 108 history << expanded 109 return result 108 # Record history *after* loading so first load gets warnings. 109 history << expanded 110 return result 111 } 110 112 end 111 113 112 114 # Is the provided constant path defined? … … 219 221 # it is not possible to load the constant into from_mod, try its parent module 220 222 # using const_missing. 221 223 def load_missing_constant(from_mod, const_name) 222 log_call from_mod, const_name 223 if from_mod == Kernel 224 if ::Object.const_defined?(const_name) 225 log "Returning Object::#{const_name} for Kernel::#{const_name}" 226 return ::Object.const_get(const_name) 227 else 228 log "Substituting Object for Kernel" 229 from_mod = Object 224 Thread.exclusive { 225 log_call from_mod, const_name 226 if from_mod == Kernel 227 if ::Object.const_defined?(const_name) 228 log "Returning Object::#{const_name} for Kernel::#{const_name}" 229 return ::Object.const_get(const_name) 230 else 231 log "Substituting Object for Kernel" 232 from_mod = Object 233 end 230 234 end 231 end232 235 233 # If we have an anonymous module, all we can do is attempt to load from Object.234 from_mod = Object if from_mod.name.blank?236 # If we have an anonymous module, all we can do is attempt to load from Object. 237 from_mod = Object if from_mod.name.blank? 235 238 236 unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id237 raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"238 end239 unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id 240 raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" 241 end 239 242 240 raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name)243 raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name) 241 244 242 qualified_name = qualified_name_for from_mod, const_name243 path_suffix = qualified_name.underscore244 name_error = NameError.new("uninitialized constant #{qualified_name}")245 qualified_name = qualified_name_for from_mod, const_name 246 path_suffix = qualified_name.underscore 247 name_error = NameError.new("uninitialized constant #{qualified_name}") 245 248 246 file_path = search_for_file(path_suffix)247 if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load248 require_or_load file_path249 raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name)250 return from_mod.const_get(const_name)251 elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)252 return mod253 elsif (parent = from_mod.parent) && parent != from_mod &&249 file_path = search_for_file(path_suffix) 250 if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load 251 require_or_load file_path 252 raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name) 253 return from_mod.const_get(const_name) 254 elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) 255 return mod 256 elsif (parent = from_mod.parent) && parent != from_mod && 254 257 ! from_mod.parents.any? { |p| p.const_defined?(const_name) } 255 # If our parents do not have a constant named +const_name+ then we are free 256 # to attempt to load upwards. If they do have such a constant, then this 257 # const_missing must be due to from_mod::const_name, which should not 258 # return constants from from_mod's parents. 259 begin 260 return parent.const_missing(const_name) 261 rescue NameError => e 262 raise unless e.missing_name? qualified_name_for(parent, const_name) 258 # If our parents do not have a constant named +const_name+ then we are free 259 # to attempt to load upwards. If they do have such a constant, then this 260 # const_missing must be due to from_mod::const_name, which should not 261 # return constants from from_mod's parents. 262 begin 263 return parent.const_missing(const_name) 264 rescue NameError => e 265 raise unless e.missing_name? qualified_name_for(parent, const_name) 266 raise name_error 267 end 268 else 263 269 raise name_error 264 270 end 265 else 266 raise name_error 267 end 271 } 268 272 end 269 273 270 274 # Remove the constants that have been autoloaded, and those that have been -
actionpack/test/template/compiled_templates_test.rb
old new 185 185 end 186 186 end 187 187 end 188 189 module ActionView190 class Base191 def compile_time192 @@compile_time193 end194 def method_names195 @@method_names196 end197 end198 end -
actionpack/lib/action_controller/dispatcher.rb
old new 3 3 # reloading the app after each request when Dependencies.load? is true. 4 4 class Dispatcher 5 5 @@guard = Mutex.new 6 7 6 class << self 8 7 # Backward-compatible class method takes CGI-specific args. Deprecated 9 8 # in favor of Dispatcher.new(output, request, response).dispatch. … … 113 112 end 114 113 115 114 def dispatch 116 @@guard.synchronize do 115 @@guard.lock unless ActionController::Base.allow_concurrency 117 116 begin 118 117 run_callbacks :before 119 118 handle_request … … 122 121 ensure 123 122 run_callbacks :after, :reverse_each 124 123 end 125 end124 @@guard.unlock unless ActionController::Base.allow_concurrency 126 125 end 127 126 128 127 def dispatch_cgi(cgi, session_options) -
actionpack/lib/action_view/base.rb
old new 158 158 attr_internal :cookies, :flash, :headers, :params, :request, :response, :session 159 159 160 160 attr_writer :template_format 161 162 attr_reader :method_names, :compile_time 161 163 162 164 # Specify trim mode for the ERB compiler. Defaults to '-'. 163 165 # See ERb documentation for suitable values. … … 189 191 190 192 delegate :request_forgery_protection_token, :to => :controller 191 193 192 @@template_handlers = HashWithIndifferentAccess.new193 194 194 module CompiledTemplates #:nodoc: 195 195 # holds compiled template code 196 196 end 197 197 include CompiledTemplates 198 198 199 # Maps inline templates to their method names200 @@method_names = {}201 # Map method names to their compile time202 @@compile_time = {}203 # Map method names to the names passed in local assigns so far204 @@template_args = {}205 # Count the number of inline templates206 @@inline_template_count = 0207 # Maps template paths without extension to their file extension returned by pick_template_extension.208 # If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions209 # used by pick_template_extension determines whether ext1 or ext2 will be stored.210 @@cached_template_extension = {}211 # Maps template paths / extensions to212 @@cached_base_paths = {}213 214 199 # Cache public asset paths 215 200 cattr_reader :computed_public_paths 216 201 @@computed_public_paths = {} … … 269 254 @assigns = assigns_for_first_render 270 255 @assigns_added = nil 271 256 @controller = controller 272 @logger = controller && controller.logger 257 @logger = controller && controller.logger 258 # Map method names to the names passed in local assigns so far 259 @template_args = {} 260 # Maps inline templates to their method names 261 @method_names = {} 262 # Map method names to their compile time 263 @compile_time = {} 264 # Count the number of inline templates 265 @inline_template_count = 0 266 # Maps template paths without extension to their file extension returned by pick_template_extension 267 # If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions 268 # used by pick_template_extension determines whether ext1 or ext2 will be stored. 269 @cached_template_extension = {} 270 # Maps template paths / extensions to 271 @cached_base_paths = {} 273 272 end 274 273 275 274 # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, … … 378 377 # 379 378 def full_template_path(template_path, extension) 380 379 if @@cache_template_extensions 381 (@ @cached_base_paths[template_path] ||= {})[extension.to_s] ||= find_full_template_path(template_path, extension)382 else380 (@cached_base_paths[template_path] ||= {})[extension.to_s] ||= find_full_template_path(template_path, extension) 381 else 383 382 find_full_template_path(template_path, extension) 384 383 end 385 384 end … … 395 394 # 396 395 def pick_template_extension(template_path)#:nodoc: 397 396 if @@cache_template_extensions 398 (@ @cached_template_extension[template_path] ||= {})[template_format] ||= find_template_extension_for(template_path)397 (@cached_template_extension[template_path] ||= {})[template_format] ||= find_template_extension_for(template_path) 399 398 else 400 399 find_template_extension_for(template_path) 401 400 end … … 458 457 # Asserts the existence of a template. 459 458 def template_exists?(template_path, extension) 460 459 file_path = full_template_path(template_path, extension) 461 !file_path.blank? && @ @method_names.has_key?(file_path) || File.exist?(file_path)460 !file_path.blank? && @method_names.has_key?(file_path) || File.exist?(file_path) 462 461 end 463 462 464 463 # Splits the path and extension from the given template_path and returns as an array. … … 522 521 # Return true if the given template was compiled for a superset of the keys in local_assigns 523 522 def supports_local_assigns?(render_symbol, local_assigns) 524 523 local_assigns.empty? || 525 ((args = @ @template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })524 ((args = @template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) 526 525 end 527 526 528 527 # Method to check whether template compilation is necessary. … … 531 530 # or if the file has changed on disk and checking file mods hasn't been disabled. 532 531 def compile_template?(template, file_name, local_assigns) 533 532 method_key = file_name || template 534 render_symbol = @ @method_names[method_key]533 render_symbol = @method_names[method_key] 535 534 536 compile_time = @ @compile_time[render_symbol]535 compile_time = @compile_time[render_symbol] 537 536 if compile_time && supports_local_assigns?(render_symbol, local_assigns) 538 537 if file_name && !@@cache_template_loading 539 538 template_changed_since?(file_name, compile_time) … … 555 554 def create_template_source(handler, template, render_symbol, locals) 556 555 body = handler.compile(template) 557 556 558 @@template_args[render_symbol] ||= {} 559 locals_keys = @@template_args[render_symbol].keys | locals 560 @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } 561 557 @template_args[render_symbol] ||= {} 558 locals_keys = @template_args[render_symbol].keys | locals 559 @template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } 562 560 locals_code = "" 563 561 locals_keys.each do |key| 564 562 locals_code << "#{key} = local_assigns[:#{key}]\n" … … 569 567 570 568 def assign_method_name(handler, template, file_name) 571 569 method_key = file_name || template 572 @ @method_names[method_key] ||= compiled_method_name(handler, template, file_name)570 @method_names[method_key] ||= compiled_method_name(handler, template, file_name) 573 571 end 574 572 575 573 def compiled_method_name(handler, template, file_name) … … 583 581 s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } 584 582 s 585 583 else 586 (@ @inline_template_count += 1).to_s584 (@inline_template_count += 1).to_s 587 585 end 588 586 end 589 587 … … 591 589 def compile_template(handler, template, file_name, local_assigns) 592 590 render_symbol = assign_method_name(handler, template, file_name) 593 591 render_source = create_template_source(handler, template, render_symbol, local_assigns.keys) 594 line_offset = @ @template_args[render_symbol].size + handler.line_offset592 line_offset = @template_args[render_symbol].size + handler.line_offset 595 593 596 594 begin 597 595 file_name = 'compiled-template' if file_name.blank? … … 606 604 raise TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e) 607 605 end 608 606 609 @ @compile_time[render_symbol] = Time.now607 @compile_time[render_symbol] = Time.now 610 608 # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger 611 609 end 612 610 … … 627 625 end 628 626 629 627 # Get the method name for this template and run it 630 method_name = @ @method_names[file_path || template]628 method_name = @method_names[file_path || template] 631 629 evaluate_assigns 632 630 633 631 send(method_name, local_assigns) do |*name|