mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Publish AS::Executor and AS::Reloader APIs
These should allow external code to run blocks of user code to do "work", at a similar unit size to a web request, without needing to get intimate with ActionDipatch.
This commit is contained in:
parent
664a13e6fb
commit
d3c9d808e3
33 changed files with 782 additions and 307 deletions
|
@ -51,8 +51,8 @@ module ActionDispatch
|
|||
autoload :Cookies
|
||||
autoload :DebugExceptions
|
||||
autoload :ExceptionWrapper
|
||||
autoload :Executor
|
||||
autoload :Flash
|
||||
autoload :LoadInterlock
|
||||
autoload :ParamsParser
|
||||
autoload :PublicExceptions
|
||||
autoload :Reloader
|
||||
|
|
|
@ -7,7 +7,16 @@ module ActionDispatch
|
|||
define_callbacks :call
|
||||
|
||||
class << self
|
||||
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
|
||||
def to_prepare(*args, &block)
|
||||
ActiveSupport::Reloader.to_prepare(*args, &block)
|
||||
end
|
||||
|
||||
def to_cleanup(*args, &block)
|
||||
ActiveSupport::Reloader.to_complete(*args, &block)
|
||||
end
|
||||
|
||||
deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
|
||||
to_cleanup: 'use ActiveSupport::Reloader.to_complete instead'
|
||||
|
||||
def before(*args, &block)
|
||||
set_callback(:call, :before, *args, &block)
|
||||
|
|
19
actionpack/lib/action_dispatch/middleware/executor.rb
Normal file
19
actionpack/lib/action_dispatch/middleware/executor.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'rack/body_proxy'
|
||||
|
||||
module ActionDispatch
|
||||
class Executor
|
||||
def initialize(app, executor)
|
||||
@app, @executor = app, executor
|
||||
end
|
||||
|
||||
def call(env)
|
||||
state = @executor.run!
|
||||
begin
|
||||
response = @app.call(env)
|
||||
returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
||||
ensure
|
||||
state.complete! unless returned
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
require 'active_support/dependencies'
|
||||
require 'rack/body_proxy'
|
||||
|
||||
module ActionDispatch
|
||||
class LoadInterlock
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
interlock = ActiveSupport::Dependencies.interlock
|
||||
interlock.start_running
|
||||
response = @app.call(env)
|
||||
body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
|
||||
response[2] = body
|
||||
response
|
||||
ensure
|
||||
interlock.done_running unless body
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,74 +23,36 @@ module ActionDispatch
|
|||
# middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
|
||||
# or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
|
||||
#
|
||||
class Reloader
|
||||
include ActiveSupport::Callbacks
|
||||
include ActiveSupport::Deprecation::Reporting
|
||||
|
||||
define_callbacks :prepare
|
||||
define_callbacks :cleanup
|
||||
|
||||
# Add a prepare callback. Prepare callbacks are run before each request, prior
|
||||
# to ActionDispatch::Callback's before callbacks.
|
||||
class Reloader < Executor
|
||||
def self.to_prepare(*args, &block)
|
||||
unless block_given?
|
||||
warn "to_prepare without a block is deprecated. Please use a block"
|
||||
end
|
||||
set_callback(:prepare, *args, &block)
|
||||
ActiveSupport::Reloader.to_prepare(*args, &block)
|
||||
end
|
||||
|
||||
# Add a cleanup callback. Cleanup callbacks are run after each request is
|
||||
# complete (after #close is called on the response body).
|
||||
def self.to_cleanup(*args, &block)
|
||||
unless block_given?
|
||||
warn "to_cleanup without a block is deprecated. Please use a block"
|
||||
end
|
||||
set_callback(:cleanup, *args, &block)
|
||||
ActiveSupport::Reloader.to_complete(*args, &block)
|
||||
end
|
||||
|
||||
# Execute all prepare callbacks.
|
||||
def self.prepare!
|
||||
new(nil).prepare!
|
||||
if defined? Rails.application.reloader
|
||||
Rails.application.reloader.prepare!
|
||||
else
|
||||
ActiveSupport::Reloader.prepare!
|
||||
end
|
||||
end
|
||||
|
||||
# Execute all cleanup callbacks.
|
||||
def self.cleanup!
|
||||
new(nil).cleanup!
|
||||
if defined? Rails.application.reloader
|
||||
Rails.application.reloader.reload!
|
||||
else
|
||||
ActiveSupport::Reloader.reload!
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, condition=nil)
|
||||
@app = app
|
||||
@condition = condition || lambda { true }
|
||||
@validated = true
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@validated = @condition.call
|
||||
prepare!
|
||||
|
||||
response = @app.call(env)
|
||||
response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
|
||||
|
||||
response
|
||||
rescue Exception
|
||||
cleanup!
|
||||
raise
|
||||
end
|
||||
|
||||
def prepare! #:nodoc:
|
||||
run_callbacks :prepare if validated?
|
||||
end
|
||||
|
||||
def cleanup! #:nodoc:
|
||||
run_callbacks :cleanup if validated?
|
||||
ensure
|
||||
@validated = true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validated? #:nodoc:
|
||||
@validated
|
||||
class << self
|
||||
deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
|
||||
to_cleanup: 'use ActiveSupport::Reloader.to_complete instead',
|
||||
prepare!: 'use Rails.application.reloader.prepare! instead',
|
||||
cleanup!: 'use Rails.application.reloader.reload! instead of cleanup + prepare'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -455,17 +455,24 @@ module ActionDispatch
|
|||
def before_setup # :nodoc:
|
||||
@app = nil
|
||||
@integration_session = nil
|
||||
@execution_context = nil
|
||||
super
|
||||
end
|
||||
|
||||
def after_teardown # :nodoc:
|
||||
remove!
|
||||
super
|
||||
end
|
||||
|
||||
def integration_session
|
||||
@integration_session ||= create_session(app)
|
||||
@integration_session ||= create_session(app).tap { @execution_context = app.respond_to?(:executor) && app.executor.run! }
|
||||
end
|
||||
|
||||
# Reset the current session. This is useful for testing multiple sessions
|
||||
# in a single test case.
|
||||
def reset!
|
||||
@integration_session = create_session(app)
|
||||
remove!
|
||||
integration_session
|
||||
end
|
||||
|
||||
def create_session(app)
|
||||
|
@ -481,6 +488,8 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
def remove! # :nodoc:
|
||||
@execution_context.complete! if @execution_context
|
||||
@execution_context = nil
|
||||
@integration_session = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -37,13 +37,19 @@ class DispatcherTest < ActiveSupport::TestCase
|
|||
|
||||
def test_to_prepare_and_cleanup_delegation
|
||||
prepared = cleaned = false
|
||||
ActionDispatch::Callbacks.to_prepare { prepared = true }
|
||||
ActionDispatch::Callbacks.to_prepare { cleaned = true }
|
||||
assert_deprecated do
|
||||
ActionDispatch::Callbacks.to_prepare { prepared = true }
|
||||
ActionDispatch::Callbacks.to_prepare { cleaned = true }
|
||||
end
|
||||
|
||||
ActionDispatch::Reloader.prepare!
|
||||
assert_deprecated do
|
||||
ActionDispatch::Reloader.prepare!
|
||||
end
|
||||
assert prepared
|
||||
|
||||
ActionDispatch::Reloader.cleanup!
|
||||
assert_deprecated do
|
||||
ActionDispatch::Reloader.cleanup!
|
||||
end
|
||||
assert cleaned
|
||||
end
|
||||
|
||||
|
|
134
actionpack/test/dispatch/executor_test.rb
Normal file
134
actionpack/test/dispatch/executor_test.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ExecutorTest < ActiveSupport::TestCase
|
||||
class MyBody < Array
|
||||
def initialize(&block)
|
||||
@on_close = block
|
||||
end
|
||||
|
||||
def foo
|
||||
"foo"
|
||||
end
|
||||
|
||||
def bar
|
||||
"bar"
|
||||
end
|
||||
|
||||
def close
|
||||
@on_close.call if @on_close
|
||||
end
|
||||
end
|
||||
|
||||
def test_returned_body_object_always_responds_to_close
|
||||
body = call_and_return_body
|
||||
assert_respond_to body, :close
|
||||
end
|
||||
|
||||
def test_returned_body_object_always_responds_to_close_even_if_called_twice
|
||||
body = call_and_return_body
|
||||
assert_respond_to body, :close
|
||||
body.close
|
||||
|
||||
body = call_and_return_body
|
||||
assert_respond_to body, :close
|
||||
body.close
|
||||
end
|
||||
|
||||
def test_returned_body_object_behaves_like_underlying_object
|
||||
body = call_and_return_body do
|
||||
b = MyBody.new
|
||||
b << "hello"
|
||||
b << "world"
|
||||
[200, { "Content-Type" => "text/html" }, b]
|
||||
end
|
||||
assert_equal 2, body.size
|
||||
assert_equal "hello", body[0]
|
||||
assert_equal "world", body[1]
|
||||
assert_equal "foo", body.foo
|
||||
assert_equal "bar", body.bar
|
||||
end
|
||||
|
||||
def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
|
||||
close_called = false
|
||||
body = call_and_return_body do
|
||||
b = MyBody.new do
|
||||
close_called = true
|
||||
end
|
||||
[200, { "Content-Type" => "text/html" }, b]
|
||||
end
|
||||
body.close
|
||||
assert close_called
|
||||
end
|
||||
|
||||
def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
|
||||
body = call_and_return_body do
|
||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||
end
|
||||
assert_respond_to body, :size
|
||||
assert_respond_to body, :each
|
||||
assert_respond_to body, :foo
|
||||
assert_respond_to body, :bar
|
||||
end
|
||||
|
||||
def test_run_callbacks_are_called_before_close
|
||||
running = false
|
||||
executor.to_run { running = true }
|
||||
|
||||
body = call_and_return_body
|
||||
assert running
|
||||
|
||||
running = false
|
||||
body.close
|
||||
assert !running
|
||||
end
|
||||
|
||||
def test_complete_callbacks_are_called_on_close
|
||||
completed = false
|
||||
executor.to_complete { completed = true }
|
||||
|
||||
body = call_and_return_body
|
||||
assert !completed
|
||||
|
||||
body.close
|
||||
assert completed
|
||||
end
|
||||
|
||||
def test_complete_callbacks_are_called_on_exceptions
|
||||
completed = false
|
||||
executor.to_complete { completed = true }
|
||||
|
||||
begin
|
||||
call_and_return_body do
|
||||
raise "error"
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
assert completed
|
||||
end
|
||||
|
||||
def test_callbacks_execute_in_shared_context
|
||||
result = false
|
||||
executor.to_run { @in_shared_context = true }
|
||||
executor.to_complete { result = @in_shared_context }
|
||||
|
||||
call_and_return_body.close
|
||||
assert result
|
||||
assert !defined?(@in_shared_context) # it's not in the test itself
|
||||
end
|
||||
|
||||
private
|
||||
def call_and_return_body(&block)
|
||||
app = middleware(block || proc { [200, {}, 'response'] })
|
||||
_, _, body = app.call({'rack.input' => StringIO.new('')})
|
||||
body
|
||||
end
|
||||
|
||||
def middleware(inner_app)
|
||||
ActionDispatch::Executor.new(inner_app, executor)
|
||||
end
|
||||
|
||||
def executor
|
||||
@executor ||= Class.new(ActiveSupport::Executor)
|
||||
end
|
||||
end
|
|
@ -4,15 +4,17 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
Reloader = ActionDispatch::Reloader
|
||||
|
||||
teardown do
|
||||
Reloader.reset_callbacks :prepare
|
||||
Reloader.reset_callbacks :cleanup
|
||||
ActiveSupport::Reloader.reset_callbacks :prepare
|
||||
ActiveSupport::Reloader.reset_callbacks :complete
|
||||
end
|
||||
|
||||
def test_prepare_callbacks
|
||||
a = b = c = nil
|
||||
Reloader.to_prepare { |*args| a = b = c = 1 }
|
||||
Reloader.to_prepare { |*args| b = c = 2 }
|
||||
Reloader.to_prepare { |*args| c = 3 }
|
||||
assert_deprecated do
|
||||
Reloader.to_prepare { |*args| a = b = c = 1 }
|
||||
Reloader.to_prepare { |*args| b = c = 2 }
|
||||
Reloader.to_prepare { |*args| c = 3 }
|
||||
end
|
||||
|
||||
# Ensure to_prepare callbacks are not run when defined
|
||||
assert_nil a || b || c
|
||||
|
@ -60,9 +62,15 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
|
||||
def test_condition_specifies_when_to_reload
|
||||
i, j = 0, 0, 0, 0
|
||||
Reloader.to_prepare { |*args| i += 1 }
|
||||
Reloader.to_cleanup { |*args| j += 1 }
|
||||
app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 })
|
||||
assert_deprecated do
|
||||
Reloader.to_prepare { |*args| i += 1 }
|
||||
Reloader.to_cleanup { |*args| j += 1 }
|
||||
end
|
||||
|
||||
x = Class.new(ActiveSupport::Reloader)
|
||||
x.check = lambda { i < 3 }
|
||||
|
||||
app = Reloader.new(lambda { |env| [200, {}, []] }, x)
|
||||
5.times do
|
||||
resp = app.call({})
|
||||
resp[2].close
|
||||
|
@ -109,7 +117,9 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
|
||||
def test_cleanup_callbacks_are_called_when_body_is_closed
|
||||
cleaned = false
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
assert_deprecated do
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
end
|
||||
|
||||
body = call_and_return_body
|
||||
assert !cleaned
|
||||
|
@ -120,7 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
|
||||
def test_prepare_callbacks_arent_called_when_body_is_closed
|
||||
prepared = false
|
||||
Reloader.to_prepare { prepared = true }
|
||||
assert_deprecated do
|
||||
Reloader.to_prepare { prepared = true }
|
||||
end
|
||||
|
||||
body = call_and_return_body
|
||||
prepared = false
|
||||
|
@ -131,31 +143,43 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
|
||||
def test_manual_reloading
|
||||
prepared = cleaned = false
|
||||
Reloader.to_prepare { prepared = true }
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
assert_deprecated do
|
||||
Reloader.to_prepare { prepared = true }
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
end
|
||||
|
||||
Reloader.prepare!
|
||||
assert_deprecated do
|
||||
Reloader.prepare!
|
||||
end
|
||||
assert prepared
|
||||
assert !cleaned
|
||||
|
||||
prepared = cleaned = false
|
||||
Reloader.cleanup!
|
||||
assert !prepared
|
||||
assert_deprecated do
|
||||
Reloader.cleanup!
|
||||
end
|
||||
assert prepared
|
||||
assert cleaned
|
||||
end
|
||||
|
||||
def test_prepend_prepare_callback
|
||||
i = 10
|
||||
Reloader.to_prepare { i += 1 }
|
||||
Reloader.to_prepare(:prepend => true) { i = 0 }
|
||||
assert_deprecated do
|
||||
Reloader.to_prepare { i += 1 }
|
||||
Reloader.to_prepare(:prepend => true) { i = 0 }
|
||||
end
|
||||
|
||||
Reloader.prepare!
|
||||
assert_deprecated do
|
||||
Reloader.prepare!
|
||||
end
|
||||
assert_equal 1, i
|
||||
end
|
||||
|
||||
def test_cleanup_callbacks_are_called_on_exceptions
|
||||
cleaned = false
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
assert_deprecated do
|
||||
Reloader.to_cleanup { cleaned = true }
|
||||
end
|
||||
|
||||
begin
|
||||
call_and_return_body do
|
||||
|
@ -169,8 +193,11 @@ class ReloaderTest < ActiveSupport::TestCase
|
|||
|
||||
private
|
||||
def call_and_return_body(&block)
|
||||
x = Class.new(ActiveSupport::Reloader)
|
||||
x.check = lambda { true }
|
||||
|
||||
@response ||= 'response'
|
||||
@reloader ||= Reloader.new(block || proc {[200, {}, @response]})
|
||||
@reloader ||= Reloader.new(block || proc {[200, {}, @response]}, x)
|
||||
@reloader.call({'rack.input' => StringIO.new('')})[2]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -137,7 +137,6 @@ module ActiveRecord
|
|||
|
||||
eager_autoload do
|
||||
autoload :AbstractAdapter
|
||||
autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -951,24 +951,5 @@ module ActiveRecord
|
|||
owner_to_pool && owner_to_pool[owner.name]
|
||||
end
|
||||
end
|
||||
|
||||
class ConnectionManagement
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
testing = env['rack.test']
|
||||
|
||||
status, headers, body = @app.call(env)
|
||||
proxy = ::Rack::BodyProxy.new(body) do
|
||||
ActiveRecord::Base.clear_active_connections! unless testing
|
||||
end
|
||||
[status, headers, proxy]
|
||||
rescue Exception
|
||||
ActiveRecord::Base.clear_active_connections! unless testing
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,6 @@ module ActiveRecord
|
|||
|
||||
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
|
||||
autoload :ConnectionHandler
|
||||
autoload :ConnectionManagement
|
||||
end
|
||||
|
||||
autoload_under 'abstract' do
|
||||
|
|
|
@ -23,34 +23,26 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
def self.install_executor_hooks(executor = ActiveSupport::Executor)
|
||||
executor.to_run do
|
||||
connection = ActiveRecord::Base.connection
|
||||
enabled = connection.query_cache_enabled
|
||||
connection_id = ActiveRecord::Base.connection_id
|
||||
connection.enable_query_cache!
|
||||
|
||||
def call(env)
|
||||
connection = ActiveRecord::Base.connection
|
||||
enabled = connection.query_cache_enabled
|
||||
connection_id = ActiveRecord::Base.connection_id
|
||||
connection.enable_query_cache!
|
||||
|
||||
response = @app.call(env)
|
||||
response[2] = Rack::BodyProxy.new(response[2]) do
|
||||
restore_query_cache_settings(connection_id, enabled)
|
||||
@restore_query_cache_settings = lambda do
|
||||
ActiveRecord::Base.connection_id = connection_id
|
||||
ActiveRecord::Base.connection.clear_query_cache
|
||||
ActiveRecord::Base.connection.disable_query_cache! unless enabled
|
||||
end
|
||||
end
|
||||
|
||||
response
|
||||
rescue Exception => e
|
||||
restore_query_cache_settings(connection_id, enabled)
|
||||
raise e
|
||||
executor.to_complete do
|
||||
@restore_query_cache_settings.call if defined?(@restore_query_cache_settings)
|
||||
|
||||
# FIXME: This should be skipped when env['rack.test']
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def restore_query_cache_settings(connection_id, enabled)
|
||||
ActiveRecord::Base.connection_id = connection_id
|
||||
ActiveRecord::Base.connection.clear_query_cache
|
||||
ActiveRecord::Base.connection.disable_query_cache! unless enabled
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,12 +16,6 @@ module ActiveRecord
|
|||
config.app_generators.orm :active_record, :migration => true,
|
||||
:timestamps => true
|
||||
|
||||
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
|
||||
ActiveRecord::QueryCache
|
||||
|
||||
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
|
||||
ActiveRecord::ConnectionAdapters::ConnectionManagement
|
||||
|
||||
config.action_dispatch.rescue_responses.merge!(
|
||||
'ActiveRecord::RecordNotFound' => :not_found,
|
||||
'ActiveRecord::StaleObjectError' => :conflict,
|
||||
|
@ -153,11 +147,9 @@ end_warning
|
|||
end
|
||||
end
|
||||
|
||||
initializer "active_record.set_reloader_hooks" do |app|
|
||||
hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
|
||||
|
||||
initializer "active_record.set_reloader_hooks" do
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActionDispatch::Reloader.send(hook) do
|
||||
ActiveSupport::Reloader.before_class_unload do
|
||||
if ActiveRecord::Base.connected?
|
||||
ActiveRecord::Base.clear_cache!
|
||||
ActiveRecord::Base.clear_reloadable_connections!
|
||||
|
@ -166,6 +158,12 @@ end_warning
|
|||
end
|
||||
end
|
||||
|
||||
initializer "active_record.set_executor_hooks" do
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::QueryCache.install_executor_hooks
|
||||
end
|
||||
end
|
||||
|
||||
initializer "active_record.add_watchable_files" do |app|
|
||||
path = app.paths["db"].first
|
||||
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
|
||||
|
|
|
@ -19,7 +19,7 @@ module ActiveRecord
|
|||
def setup
|
||||
@env = {}
|
||||
@app = App.new
|
||||
@management = ConnectionManagement.new(@app)
|
||||
@management = middleware(@app)
|
||||
|
||||
# make sure we have an active connection
|
||||
assert ActiveRecord::Base.connection
|
||||
|
@ -27,17 +27,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def test_app_delegation
|
||||
manager = ConnectionManagement.new(@app)
|
||||
manager = middleware(@app)
|
||||
|
||||
manager.call @env
|
||||
assert_equal [@env], @app.calls
|
||||
end
|
||||
|
||||
def test_connections_are_active_after_call
|
||||
@management.call(@env)
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
|
||||
def test_body_responds_to_each
|
||||
_, _, body = @management.call(@env)
|
||||
bits = []
|
||||
|
@ -52,45 +47,40 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def test_active_connections_are_not_cleared_on_body_close_during_test
|
||||
@env['rack.test'] = true
|
||||
_, _, body = @management.call(@env)
|
||||
body.close
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
executor.wrap do
|
||||
_, _, body = @management.call(@env)
|
||||
body.close
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
end
|
||||
|
||||
def test_connections_closed_if_exception
|
||||
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
|
||||
explosive = ConnectionManagement.new(app)
|
||||
explosive = middleware(app)
|
||||
assert_raises(NotImplementedError) { explosive.call(@env) }
|
||||
assert !ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
|
||||
def test_connections_not_closed_if_exception_and_test
|
||||
@env['rack.test'] = true
|
||||
app = Class.new(App) { def call(env); raise; end }.new
|
||||
explosive = ConnectionManagement.new(app)
|
||||
assert_raises(RuntimeError) { explosive.call(@env) }
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
|
||||
def test_connections_closed_if_exception_and_explicitly_not_test
|
||||
@env['rack.test'] = false
|
||||
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
|
||||
explosive = ConnectionManagement.new(app)
|
||||
assert_raises(NotImplementedError) { explosive.call(@env) }
|
||||
assert !ActiveRecord::Base.connection_handler.active_connections?
|
||||
executor.wrap do
|
||||
app = Class.new(App) { def call(env); raise; end }.new
|
||||
explosive = middleware(app)
|
||||
assert_raises(RuntimeError) { explosive.call(@env) }
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't clear active connections when running in a test case" do
|
||||
@env['rack.test'] = true
|
||||
@management.call(@env)
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
executor.wrap do
|
||||
@management.call(@env)
|
||||
assert ActiveRecord::Base.connection_handler.active_connections?
|
||||
end
|
||||
end
|
||||
|
||||
test "proxy is polite to its body and responds to it" do
|
||||
body = Class.new(String) { def to_path; "/path"; end }.new
|
||||
app = lambda { |_| [200, {}, body] }
|
||||
response_body = ConnectionManagement.new(app).call(@env)[2]
|
||||
response_body = middleware(app).call(@env)[2]
|
||||
assert response_body.respond_to?(:to_path)
|
||||
assert_equal "/path", response_body.to_path
|
||||
end
|
||||
|
@ -98,9 +88,23 @@ module ActiveRecord
|
|||
test "doesn't mutate the original response" do
|
||||
original_response = [200, {}, 'hi']
|
||||
app = lambda { |_| original_response }
|
||||
ConnectionManagement.new(app).call(@env)[2]
|
||||
middleware(app).call(@env)[2]
|
||||
assert_equal 'hi', original_response.last
|
||||
end
|
||||
|
||||
private
|
||||
def executor
|
||||
@executor ||= Class.new(ActiveSupport::Executor).tap do |exe|
|
||||
ActiveRecord::QueryCache.install_executor_hooks(exe)
|
||||
end
|
||||
end
|
||||
|
||||
def middleware(app)
|
||||
lambda do |env|
|
||||
a, b, c = executor.wrap { app.call(env) }
|
||||
[a, b, Rack::BodyProxy.new(c) { }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
def test_exceptional_middleware_clears_and_disables_cache_on_error
|
||||
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
|
||||
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
Task.find 1
|
||||
Task.find 1
|
||||
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
|
||||
|
@ -31,7 +31,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
def test_exceptional_middleware_leaves_enabled_cache_alone
|
||||
ActiveRecord::Base.connection.enable_query_cache!
|
||||
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
raise "lol borked"
|
||||
}
|
||||
assert_raises(RuntimeError) { mw.call({}) }
|
||||
|
@ -42,7 +42,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
def test_exceptional_middleware_assigns_original_connection_id_on_error
|
||||
connection_id = ActiveRecord::Base.connection_id
|
||||
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
ActiveRecord::Base.connection_id = self.object_id
|
||||
raise "lol borked"
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
|
||||
def test_middleware_delegates
|
||||
called = false
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
called = true
|
||||
[200, {}, nil]
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_middleware_caches
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
Task.find 1
|
||||
Task.find 1
|
||||
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
|
||||
|
@ -74,50 +74,13 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
def test_cache_enabled_during_call
|
||||
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
|
||||
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
mw = middleware { |env|
|
||||
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
|
||||
[200, {}, nil]
|
||||
}
|
||||
mw.call({})
|
||||
end
|
||||
|
||||
def test_cache_on_during_body_write
|
||||
streaming = Class.new do
|
||||
def each
|
||||
yield ActiveRecord::Base.connection.query_cache_enabled
|
||||
end
|
||||
end
|
||||
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
[200, {}, streaming.new]
|
||||
}
|
||||
body = mw.call({}).last
|
||||
body.each { |x| assert x, 'cache should be on' }
|
||||
body.close
|
||||
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
|
||||
end
|
||||
|
||||
def test_cache_off_after_close
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
|
||||
body = mw.call({}).last
|
||||
|
||||
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
|
||||
body.close
|
||||
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
|
||||
end
|
||||
|
||||
def test_cache_clear_after_close
|
||||
mw = ActiveRecord::QueryCache.new lambda { |env|
|
||||
Post.first
|
||||
[200, {}, nil]
|
||||
}
|
||||
body = mw.call({}).last
|
||||
|
||||
assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
|
||||
body.close
|
||||
assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
|
||||
end
|
||||
|
||||
def test_cache_passing_a_relation
|
||||
post = Post.first
|
||||
Post.cache do
|
||||
|
@ -244,6 +207,13 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
assert_equal 0, Post.where(title: 'rollback').to_a.count
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def middleware(&app)
|
||||
executor = Class.new(ActiveSupport::Executor)
|
||||
ActiveRecord::QueryCache.install_executor_hooks executor
|
||||
lambda { |env| executor.wrap { app.call(env) } }
|
||||
end
|
||||
end
|
||||
|
||||
class QueryCacheExpiryTest < ActiveRecord::TestCase
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
* Publish ActiveSupport::Executor and ActiveSupport::Reloader APIs to allow
|
||||
components and libraries to manage, and participate in, the execution of
|
||||
application code, and the application reloading process.
|
||||
|
||||
*Matthew Draper*
|
||||
|
||||
|
||||
## Rails 5.0.0.beta3 (February 24, 2016) ##
|
||||
|
||||
* Deprecate arguments on `assert_nothing_raised`.
|
||||
|
|
|
@ -33,10 +33,13 @@ module ActiveSupport
|
|||
autoload :Concern
|
||||
autoload :Dependencies
|
||||
autoload :DescendantsTracker
|
||||
autoload :ExecutionWrapper
|
||||
autoload :Executor
|
||||
autoload :FileUpdateChecker
|
||||
autoload :EventedFileUpdateChecker
|
||||
autoload :LogSubscriber
|
||||
autoload :Notifications
|
||||
autoload :Reloader
|
||||
|
||||
eager_autoload do
|
||||
autoload :BacktraceCleaner
|
||||
|
|
|
@ -19,14 +19,12 @@ module ActiveSupport #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Attempt to obtain an "unloading" (exclusive) lock. If possible,
|
||||
# execute the supplied block while holding the lock. If there is
|
||||
# concurrent activity, return immediately (without executing the
|
||||
# block) instead of waiting.
|
||||
def attempt_unloading
|
||||
@lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do
|
||||
yield
|
||||
end
|
||||
def start_unloading
|
||||
@lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
|
||||
end
|
||||
|
||||
def done_unloading
|
||||
@lock.stop_exclusive(compatible: [:load, :unload])
|
||||
end
|
||||
|
||||
def start_running
|
||||
|
|
|
@ -37,6 +37,7 @@ module ActiveSupport
|
|||
|
||||
def execute_if_updated
|
||||
if updated?
|
||||
yield if block_given?
|
||||
execute
|
||||
true
|
||||
end
|
||||
|
|
69
activesupport/lib/active_support/execution_wrapper.rb
Normal file
69
activesupport/lib/active_support/execution_wrapper.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
require 'active_support/callbacks'
|
||||
|
||||
module ActiveSupport
|
||||
class ExecutionWrapper
|
||||
include ActiveSupport::Callbacks
|
||||
|
||||
define_callbacks :run
|
||||
define_callbacks :complete
|
||||
|
||||
def self.to_run(*args, &block)
|
||||
set_callback(:run, *args, &block)
|
||||
end
|
||||
|
||||
def self.to_complete(*args, &block)
|
||||
set_callback(:complete, *args, &block)
|
||||
end
|
||||
|
||||
# Run this execution.
|
||||
#
|
||||
# Returns an instance, whose +complete!+ method *must* be invoked
|
||||
# after the work has been performed.
|
||||
#
|
||||
# Where possible, prefer +wrap+.
|
||||
def self.run!
|
||||
new.tap(&:run!)
|
||||
end
|
||||
|
||||
# Perform the work in the supplied block as an execution.
|
||||
def self.wrap
|
||||
return yield if active?
|
||||
|
||||
state = run!
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
state.complete!
|
||||
end
|
||||
end
|
||||
|
||||
class << self # :nodoc:
|
||||
attr_accessor :active
|
||||
end
|
||||
|
||||
def self.inherited(other) # :nodoc:
|
||||
super
|
||||
other.active = Concurrent::Hash.new(0)
|
||||
end
|
||||
|
||||
self.active = Concurrent::Hash.new(0)
|
||||
|
||||
def self.active? # :nodoc:
|
||||
@active[Thread.current] > 0
|
||||
end
|
||||
|
||||
def run! # :nodoc:
|
||||
self.class.active[Thread.current] += 1
|
||||
run_callbacks(:run)
|
||||
end
|
||||
|
||||
# Complete this in-flight execution. This method *must* be called
|
||||
# exactly once on the result of any call to +run!+.
|
||||
#
|
||||
# Where possible, prefer +wrap+.
|
||||
def complete!
|
||||
run_callbacks(:complete)
|
||||
self.class.active.delete Thread.current if (self.class.active[Thread.current] -= 1) == 0
|
||||
end
|
||||
end
|
||||
end
|
6
activesupport/lib/active_support/executor.rb
Normal file
6
activesupport/lib/active_support/executor.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
require 'active_support/execution_wrapper'
|
||||
|
||||
module ActiveSupport
|
||||
class Executor < ExecutionWrapper
|
||||
end
|
||||
end
|
|
@ -81,6 +81,7 @@ module ActiveSupport
|
|||
# Execute the block given if updated.
|
||||
def execute_if_updated
|
||||
if updated?
|
||||
yield if block_given?
|
||||
execute
|
||||
true
|
||||
else
|
||||
|
|
|
@ -64,8 +64,8 @@ module I18n
|
|||
end
|
||||
|
||||
app.reloaders << reloader
|
||||
ActionDispatch::Reloader.to_prepare do
|
||||
reloader.execute_if_updated
|
||||
app.reloader.to_run do
|
||||
reloader.execute_if_updated { require_unload_lock! }
|
||||
# TODO: remove the following line as soon as the return value of
|
||||
# callbacks is ignored, that is, returning `false` does not
|
||||
# display a deprecation warning or halts the callback chain.
|
||||
|
|
126
activesupport/lib/active_support/reloader.rb
Normal file
126
activesupport/lib/active_support/reloader.rb
Normal file
|
@ -0,0 +1,126 @@
|
|||
require 'active_support/execution_wrapper'
|
||||
|
||||
module ActiveSupport
|
||||
#--
|
||||
# This class defines several callbacks:
|
||||
#
|
||||
# to_prepare -- Run once at application startup, and also from
|
||||
# +to_run+.
|
||||
#
|
||||
# to_run -- Run before a work run that is reloading. If
|
||||
# +reload_classes_only_on_change+ is true (the default), the class
|
||||
# unload will have already occurred.
|
||||
#
|
||||
# to_complete -- Run after a work run that has reloaded. If
|
||||
# +reload_classes_only_on_change+ is false, the class unload will
|
||||
# have occurred after the work run, but before this callback.
|
||||
#
|
||||
# before_class_unload -- Run immediately before the classes are
|
||||
# unloaded.
|
||||
#
|
||||
# after_class_unload -- Run immediately after the classes are
|
||||
# unloaded.
|
||||
#
|
||||
class Reloader < ExecutionWrapper
|
||||
Null = Class.new(ExecutionWrapper) # :nodoc:
|
||||
|
||||
define_callbacks :prepare
|
||||
|
||||
define_callbacks :class_unload
|
||||
|
||||
def self.to_prepare(*args, &block)
|
||||
set_callback(:prepare, *args, &block)
|
||||
end
|
||||
|
||||
def self.before_class_unload(*args, &block)
|
||||
set_callback(:class_unload, *args, &block)
|
||||
end
|
||||
|
||||
def self.after_class_unload(*args, &block)
|
||||
set_callback(:class_unload, :after, *args, &block)
|
||||
end
|
||||
|
||||
to_run(:after) { self.class.prepare! }
|
||||
|
||||
# Initiate a manual reload
|
||||
def self.reload!
|
||||
executor.wrap do
|
||||
new.tap(&:run!).complete!
|
||||
end
|
||||
prepare!
|
||||
end
|
||||
|
||||
def self.run! # :nodoc:
|
||||
if check!
|
||||
super
|
||||
else
|
||||
Null.run!
|
||||
end
|
||||
end
|
||||
|
||||
# Run the supplied block as a work unit, reloading code as needed
|
||||
def self.wrap
|
||||
executor.wrap do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :executor
|
||||
attr_accessor :check
|
||||
end
|
||||
self.executor = Executor
|
||||
self.check = lambda { false }
|
||||
|
||||
def self.check! # :nodoc:
|
||||
@should_reload ||= check.call
|
||||
end
|
||||
|
||||
def self.reloaded! # :nodoc:
|
||||
@should_reload = false
|
||||
end
|
||||
|
||||
def self.prepare! # :nodoc:
|
||||
new.run_callbacks(:prepare)
|
||||
end
|
||||
|
||||
def initialize
|
||||
super
|
||||
@locked = false
|
||||
end
|
||||
|
||||
# Acquire the ActiveSupport::Dependencies::Interlock unload lock,
|
||||
# ensuring it will be released automatically
|
||||
def require_unload_lock!
|
||||
unless @locked
|
||||
ActiveSupport::Dependencies.interlock.start_unloading
|
||||
@locked = true
|
||||
end
|
||||
end
|
||||
|
||||
# Release the unload lock if it has been previously obtained
|
||||
def release_unload_lock!
|
||||
if @locked
|
||||
@locked = false
|
||||
ActiveSupport::Dependencies.interlock.done_unloading
|
||||
end
|
||||
end
|
||||
|
||||
def run! # :nodoc:
|
||||
super
|
||||
release_unload_lock!
|
||||
end
|
||||
|
||||
def class_unload!(&block) # :nodoc:
|
||||
require_unload_lock!
|
||||
run_callbacks(:class_unload, &block)
|
||||
end
|
||||
|
||||
def complete! # :nodoc:
|
||||
super
|
||||
self.class.reloaded!
|
||||
ensure
|
||||
release_unload_lock!
|
||||
end
|
||||
end
|
||||
end
|
76
activesupport/test/executor_test.rb
Normal file
76
activesupport/test/executor_test.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ExecutorTest < ActiveSupport::TestCase
|
||||
def test_wrap_invokes_callbacks
|
||||
called = []
|
||||
executor.to_run { called << :run }
|
||||
executor.to_complete { called << :complete }
|
||||
|
||||
executor.wrap do
|
||||
called << :body
|
||||
end
|
||||
|
||||
assert_equal [:run, :body, :complete], called
|
||||
end
|
||||
|
||||
def test_callbacks_share_state
|
||||
result = false
|
||||
executor.to_run { @foo = true }
|
||||
executor.to_complete { result = @foo }
|
||||
|
||||
executor.wrap { }
|
||||
|
||||
assert result
|
||||
end
|
||||
|
||||
def test_separated_calls_invoke_callbacks
|
||||
called = []
|
||||
executor.to_run { called << :run }
|
||||
executor.to_complete { called << :complete }
|
||||
|
||||
state = executor.run!
|
||||
called << :body
|
||||
state.complete!
|
||||
|
||||
assert_equal [:run, :body, :complete], called
|
||||
end
|
||||
|
||||
def test_avoids_double_wrapping
|
||||
called = []
|
||||
executor.to_run { called << :run }
|
||||
executor.to_complete { called << :complete }
|
||||
|
||||
executor.wrap do
|
||||
called << :early
|
||||
executor.wrap do
|
||||
called << :body
|
||||
end
|
||||
called << :late
|
||||
end
|
||||
|
||||
assert_equal [:run, :early, :body, :late, :complete], called
|
||||
end
|
||||
|
||||
def test_separate_classes_can_wrap
|
||||
other_executor = Class.new(ActiveSupport::Executor)
|
||||
|
||||
called = []
|
||||
executor.to_run { called << :run }
|
||||
executor.to_complete { called << :complete }
|
||||
other_executor.to_run { called << :other_run }
|
||||
other_executor.to_complete { called << :other_complete }
|
||||
|
||||
executor.wrap do
|
||||
other_executor.wrap do
|
||||
called << :body
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal [:run, :other_run, :body, :other_complete, :complete], called
|
||||
end
|
||||
|
||||
private
|
||||
def executor
|
||||
@executor ||= Class.new(ActiveSupport::Executor)
|
||||
end
|
||||
end
|
85
activesupport/test/reloader_test.rb
Normal file
85
activesupport/test/reloader_test.rb
Normal file
|
@ -0,0 +1,85 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class ReloaderTest < ActiveSupport::TestCase
|
||||
def test_prepare_callback
|
||||
prepared = false
|
||||
reloader.to_prepare { prepared = true }
|
||||
|
||||
assert !prepared
|
||||
reloader.prepare!
|
||||
assert prepared
|
||||
|
||||
prepared = false
|
||||
reloader.wrap do
|
||||
assert prepared
|
||||
prepared = false
|
||||
end
|
||||
assert !prepared
|
||||
end
|
||||
|
||||
def test_only_run_when_check_passes
|
||||
r = new_reloader { true }
|
||||
invoked = false
|
||||
r.to_run { invoked = true }
|
||||
r.wrap { }
|
||||
assert invoked
|
||||
|
||||
r = new_reloader { false }
|
||||
invoked = false
|
||||
r.to_run { invoked = true }
|
||||
r.wrap { }
|
||||
assert !invoked
|
||||
end
|
||||
|
||||
def test_full_reload_sequence
|
||||
called = []
|
||||
reloader.to_prepare { called << :prepare }
|
||||
reloader.to_run { called << :reloader_run }
|
||||
reloader.to_complete { called << :reloader_complete }
|
||||
reloader.executor.to_run { called << :executor_run }
|
||||
reloader.executor.to_complete { called << :executor_complete }
|
||||
|
||||
reloader.wrap { }
|
||||
assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called
|
||||
|
||||
called = []
|
||||
reloader.reload!
|
||||
assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
|
||||
|
||||
reloader.check = lambda { false }
|
||||
|
||||
called = []
|
||||
reloader.wrap { }
|
||||
assert_equal [:executor_run, :executor_complete], called
|
||||
|
||||
called = []
|
||||
reloader.reload!
|
||||
assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
|
||||
end
|
||||
|
||||
def test_class_unload_block
|
||||
called = []
|
||||
reloader.before_class_unload { called << :before_unload }
|
||||
reloader.after_class_unload { called << :after_unload }
|
||||
reloader.to_run do
|
||||
class_unload! do
|
||||
called << :unload
|
||||
end
|
||||
end
|
||||
reloader.wrap { called << :body }
|
||||
|
||||
assert_equal [:before_unload, :unload, :after_unload, :body], called
|
||||
end
|
||||
|
||||
private
|
||||
def new_reloader(&check)
|
||||
Class.new(ActiveSupport::Reloader).tap do |r|
|
||||
r.check = check
|
||||
r.executor = Class.new(ActiveSupport::Executor)
|
||||
end
|
||||
end
|
||||
|
||||
def reloader
|
||||
@reloader ||= new_reloader { true }
|
||||
end
|
||||
end
|
|
@ -113,7 +113,7 @@ module Rails
|
|||
|
||||
attr_accessor :assets, :sandbox
|
||||
alias_method :sandbox?, :sandbox
|
||||
attr_reader :reloaders
|
||||
attr_reader :reloaders, :reloader, :executor
|
||||
|
||||
delegate :default_url_options, :default_url_options=, to: :routes
|
||||
|
||||
|
@ -131,6 +131,10 @@ module Rails
|
|||
@message_verifiers = {}
|
||||
@ran_load_hooks = false
|
||||
|
||||
@executor = Class.new(ActiveSupport::Executor)
|
||||
@reloader = Class.new(ActiveSupport::Reloader)
|
||||
@reloader.executor = @executor
|
||||
|
||||
# are these actually used?
|
||||
@initial_variable_values = initial_variable_values
|
||||
@block = block
|
||||
|
|
|
@ -34,22 +34,10 @@ module Rails
|
|||
# handling: presumably their code is not threadsafe
|
||||
|
||||
middleware.use ::Rack::Lock
|
||||
|
||||
elsif config.allow_concurrency == :unsafe
|
||||
# Do nothing, even if we know this is dangerous. This is the
|
||||
# historical behaviour for true.
|
||||
|
||||
else
|
||||
# Default concurrency setting: enabled, but safe
|
||||
|
||||
unless config.cache_classes && config.eager_load
|
||||
# Without cache_classes + eager_load, the load interlock
|
||||
# is required for proper operation
|
||||
|
||||
middleware.use ::ActionDispatch::LoadInterlock
|
||||
end
|
||||
end
|
||||
|
||||
middleware.use ::ActionDispatch::Executor, app.executor
|
||||
|
||||
middleware.use ::Rack::Runtime
|
||||
middleware.use ::Rack::MethodOverride unless config.api_only
|
||||
middleware.use ::ActionDispatch::RequestId
|
||||
|
@ -61,7 +49,7 @@ module Rails
|
|||
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
|
||||
|
||||
unless config.cache_classes
|
||||
middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? }
|
||||
middleware.use ::ActionDispatch::Reloader, app.reloader
|
||||
end
|
||||
|
||||
middleware.use ::ActionDispatch::Callbacks
|
||||
|
@ -83,10 +71,6 @@ module Rails
|
|||
|
||||
private
|
||||
|
||||
def reload_dependencies?
|
||||
config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
|
||||
end
|
||||
|
||||
def load_rack_cache
|
||||
rack_cache = config.action_dispatch.rack_cache
|
||||
return unless rack_cache
|
||||
|
|
|
@ -38,16 +38,16 @@ module Rails
|
|||
app.routes.define_mounted_helper(:main_app)
|
||||
end
|
||||
|
||||
initializer :add_to_prepare_blocks do
|
||||
initializer :add_to_prepare_blocks do |app|
|
||||
config.to_prepare_blocks.each do |block|
|
||||
ActionDispatch::Reloader.to_prepare(&block)
|
||||
app.reloader.to_prepare(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# This needs to happen before eager load so it happens
|
||||
# in exactly the same point regardless of config.cache_classes
|
||||
initializer :run_prepare_callbacks do
|
||||
ActionDispatch::Reloader.prepare!
|
||||
initializer :run_prepare_callbacks do |app|
|
||||
app.reloader.prepare!
|
||||
end
|
||||
|
||||
initializer :eager_load! do
|
||||
|
@ -62,13 +62,47 @@ module Rails
|
|||
ActiveSupport.run_load_hooks(:after_initialize, self)
|
||||
end
|
||||
|
||||
initializer :configure_executor_for_concurrency do |app|
|
||||
if config.allow_concurrency == false
|
||||
# User has explicitly opted out of concurrent request
|
||||
# handling: presumably their code is not threadsafe
|
||||
|
||||
mutex = Mutex.new
|
||||
app.executor.to_run(prepend: true) do
|
||||
mutex.lock
|
||||
end
|
||||
app.executor.to_complete(:after) do
|
||||
mutex.unlock
|
||||
end
|
||||
|
||||
elsif config.allow_concurrency == :unsafe
|
||||
# Do nothing, even if we know this is dangerous. This is the
|
||||
# historical behaviour for true.
|
||||
|
||||
else
|
||||
# Default concurrency setting: enabled, but safe
|
||||
|
||||
unless config.cache_classes && config.eager_load
|
||||
# Without cache_classes + eager_load, the load interlock
|
||||
# is required for proper operation
|
||||
|
||||
app.executor.to_run(prepend: true) do
|
||||
ActiveSupport::Dependencies.interlock.start_running
|
||||
end
|
||||
app.executor.to_complete(:after) do
|
||||
ActiveSupport::Dependencies.interlock.done_running
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set routes reload after the finisher hook to ensure routes added in
|
||||
# the hook are taken into account.
|
||||
initializer :set_routes_reloader_hook do
|
||||
initializer :set_routes_reloader_hook do |app|
|
||||
reloader = routes_reloader
|
||||
reloader.execute_if_updated
|
||||
self.reloaders << reloader
|
||||
ActionDispatch::Reloader.to_prepare do
|
||||
app.reloader.to_run do
|
||||
# We configure #execute rather than #execute_if_updated because if
|
||||
# autoloaded constants are cleared we need to reload routes also in
|
||||
# case any was used there, as in
|
||||
|
@ -78,18 +112,27 @@ module Rails
|
|||
# This means routes are also reloaded if i18n is updated, which
|
||||
# might not be necessary, but in order to be more precise we need
|
||||
# some sort of reloaders dependency support, to be added.
|
||||
require_unload_lock!
|
||||
reloader.execute
|
||||
end
|
||||
end
|
||||
|
||||
# Set clearing dependencies after the finisher hook to ensure paths
|
||||
# added in the hook are taken into account.
|
||||
initializer :set_clear_dependencies_hook, group: :all do
|
||||
initializer :set_clear_dependencies_hook, group: :all do |app|
|
||||
callback = lambda do
|
||||
ActiveSupport::Dependencies.interlock.unloading do
|
||||
ActiveSupport::DescendantsTracker.clear
|
||||
ActiveSupport::Dependencies.clear
|
||||
ActiveSupport::DescendantsTracker.clear
|
||||
ActiveSupport::Dependencies.clear
|
||||
end
|
||||
|
||||
if config.cache_classes
|
||||
app.reloader.check = lambda { false }
|
||||
elsif config.reload_classes_only_on_change
|
||||
app.reloader.check = lambda do
|
||||
app.reloaders.map(&:updated?).any?
|
||||
end
|
||||
else
|
||||
app.reloader.check = lambda { true }
|
||||
end
|
||||
|
||||
if config.reload_classes_only_on_change
|
||||
|
@ -99,15 +142,19 @@ module Rails
|
|||
# Prepend this callback to have autoloaded constants cleared before
|
||||
# any other possible reloading, in case they need to autoload fresh
|
||||
# constants.
|
||||
ActionDispatch::Reloader.to_prepare(prepend: true) do
|
||||
app.reloader.to_run(prepend: true) do
|
||||
# In addition to changes detected by the file watcher, if routes
|
||||
# or i18n have been updated we also need to clear constants,
|
||||
# that's why we run #execute rather than #execute_if_updated, this
|
||||
# callback has to clear autoloaded constants after any update.
|
||||
reloader.execute
|
||||
class_unload! do
|
||||
reloader.execute
|
||||
end
|
||||
end
|
||||
else
|
||||
ActionDispatch::Reloader.to_cleanup(&callback)
|
||||
app.reloader.to_complete do
|
||||
class_unload!(&callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -29,8 +29,7 @@ module Rails
|
|||
# reloads the environment
|
||||
def reload!(print=true)
|
||||
puts "Reloading..." if print
|
||||
ActionDispatch::Reloader.cleanup!
|
||||
ActionDispatch::Reloader.prepare!
|
||||
Rails.application.reloader.reload!
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,12 +52,11 @@ class ConsoleTest < ActiveSupport::TestCase
|
|||
a = b = c = nil
|
||||
|
||||
# TODO: These should be defined on the initializer
|
||||
ActionDispatch::Reloader.to_cleanup { a = b = c = 1 }
|
||||
ActionDispatch::Reloader.to_cleanup { b = c = 2 }
|
||||
ActionDispatch::Reloader.to_prepare { c = 3 }
|
||||
ActiveSupport::Reloader.to_complete { a = b = c = 1 }
|
||||
ActiveSupport::Reloader.to_complete { b = c = 2 }
|
||||
ActiveSupport::Reloader.to_prepare { c = 3 }
|
||||
|
||||
# Hide Reloading... output
|
||||
silence_stream(STDOUT) { irb_context.reload! }
|
||||
irb_context.reload!(false)
|
||||
|
||||
assert_equal 1, a
|
||||
assert_equal 2, b
|
||||
|
@ -81,7 +80,7 @@ class ConsoleTest < ActiveSupport::TestCase
|
|||
MODEL
|
||||
|
||||
assert !User.new.respond_to?(:age)
|
||||
silence_stream(STDOUT) { irb_context.reload! }
|
||||
irb_context.reload!(false)
|
||||
assert User.new.respond_to?(:age)
|
||||
end
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module ApplicationTests
|
|||
assert_equal [
|
||||
"Rack::Sendfile",
|
||||
"ActionDispatch::Static",
|
||||
"ActionDispatch::LoadInterlock",
|
||||
"ActionDispatch::Executor",
|
||||
"ActiveSupport::Cache::Strategy::LocalCache",
|
||||
"Rack::Runtime",
|
||||
"Rack::MethodOverride",
|
||||
|
@ -38,8 +38,6 @@ module ApplicationTests
|
|||
"ActionDispatch::Reloader",
|
||||
"ActionDispatch::Callbacks",
|
||||
"ActiveRecord::Migration::CheckPending",
|
||||
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
|
||||
"ActiveRecord::QueryCache",
|
||||
"ActionDispatch::Cookies",
|
||||
"ActionDispatch::Session::CookieStore",
|
||||
"ActionDispatch::Flash",
|
||||
|
@ -57,7 +55,7 @@ module ApplicationTests
|
|||
assert_equal [
|
||||
"Rack::Sendfile",
|
||||
"ActionDispatch::Static",
|
||||
"ActionDispatch::LoadInterlock",
|
||||
"ActionDispatch::Executor",
|
||||
"ActiveSupport::Cache::Strategy::LocalCache",
|
||||
"Rack::Runtime",
|
||||
"ActionDispatch::RequestId",
|
||||
|
@ -67,8 +65,6 @@ module ApplicationTests
|
|||
"ActionDispatch::RemoteIp",
|
||||
"ActionDispatch::Reloader",
|
||||
"ActionDispatch::Callbacks",
|
||||
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
|
||||
"ActiveRecord::QueryCache",
|
||||
"Rack::Head",
|
||||
"Rack::ConditionalGet",
|
||||
"Rack::ETag"
|
||||
|
@ -114,23 +110,12 @@ module ApplicationTests
|
|||
test "removing Active Record omits its middleware" do
|
||||
use_frameworks []
|
||||
boot!
|
||||
assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
|
||||
assert !middleware.include?("ActiveRecord::QueryCache")
|
||||
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
|
||||
end
|
||||
|
||||
test "includes interlock if cache_classes is set but eager_load is not" do
|
||||
add_to_config "config.cache_classes = true"
|
||||
test "includes executor" do
|
||||
boot!
|
||||
assert_not_includes middleware, "Rack::Lock"
|
||||
assert_includes middleware, "ActionDispatch::LoadInterlock"
|
||||
end
|
||||
|
||||
test "includes interlock if cache_classes is off" do
|
||||
add_to_config "config.cache_classes = false"
|
||||
boot!
|
||||
assert_not_includes middleware, "Rack::Lock"
|
||||
assert_includes middleware, "ActionDispatch::LoadInterlock"
|
||||
assert_includes middleware, "ActionDispatch::Executor"
|
||||
end
|
||||
|
||||
test "does not include lock if cache_classes is set and so is eager_load" do
|
||||
|
@ -138,21 +123,18 @@ module ApplicationTests
|
|||
add_to_config "config.eager_load = true"
|
||||
boot!
|
||||
assert_not_includes middleware, "Rack::Lock"
|
||||
assert_not_includes middleware, "ActionDispatch::LoadInterlock"
|
||||
end
|
||||
|
||||
test "does not include lock if allow_concurrency is set to :unsafe" do
|
||||
add_to_config "config.allow_concurrency = :unsafe"
|
||||
boot!
|
||||
assert_not_includes middleware, "Rack::Lock"
|
||||
assert_not_includes middleware, "ActionDispatch::LoadInterlock"
|
||||
end
|
||||
|
||||
test "includes lock if allow_concurrency is disabled" do
|
||||
add_to_config "config.allow_concurrency = false"
|
||||
boot!
|
||||
assert_includes middleware, "Rack::Lock"
|
||||
assert_not_includes middleware, "ActionDispatch::LoadInterlock"
|
||||
end
|
||||
|
||||
test "removes static asset server if public_file_server.enabled is disabled" do
|
||||
|
|
Loading…
Reference in a new issue