diff options
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r-- | actionpack/lib/action_controller/log_subscriber.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_controller/metal/live.rb | 66 | ||||
-rw-r--r-- | actionpack/lib/action_controller/test_case.rb | 10 |
3 files changed, 52 insertions, 26 deletions
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index d1d6acac26..450a04779f 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -26,7 +26,7 @@ module ActionController end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ".freeze)})" unless additions.blank? - message << "\n\n" if Rails.env.development? + message << "\n\n" if defined?(Rails.env) && Rails.env.development? message end diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index e3c540bf5f..fc20e7a421 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -237,39 +237,55 @@ module ActionController # This processes the action in a child thread. It lets us return the # response code and headers back up the rack stack, and still process # the body in parallel with sending data to the client - Thread.new { - t2 = Thread.current - t2.abort_on_exception = true - - # Since we're processing the view in a different thread, copy the - # thread locals from the main thread to the child thread. :'( - locals.each { |k,v| t2[k] = v } - - begin - super(name) - rescue => e - if @_response.committed? - begin - @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html - @_response.stream.call_on_error - rescue => exception - log_error(exception) - ensure - log_error(e) - @_response.stream.close + new_controller_thread { + ActiveSupport::Dependencies.interlock.running do + t2 = Thread.current + + # Since we're processing the view in a different thread, copy the + # thread locals from the main thread to the child thread. :'( + locals.each { |k,v| t2[k] = v } + + begin + super(name) + rescue => e + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else + error = e end - else - error = e + ensure + @_response.commit! end - ensure - @_response.commit! end } - @_response.await_commit + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @_response.await_commit + end + raise error if error end + # Spawn a new thread to serve up the controller in. This is to get + # around the fact that Rack isn't based around IOs and we need to use + # a thread to stream data from the response bodies. Nobody should call + # this method except in Rails internals. Seriously! + def new_controller_thread # :nodoc: + Thread.new { + t2 = Thread.current + t2.abort_on_exception = true + yield + } + end + def log_error(exception) logger = ActionController::Base.logger return unless logger diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b43bb9dc17..ac45b2e363 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,6 +12,16 @@ module ActionController include Testing::Functional end + module Live + # Disable controller / rendering threads in tests. User tests can access + # the database on the main thread, so they could open a txn, then the + # controller thread will open a new connection and try to access data + # that's only visible to the main thread's txn. This is the problem in #23483 + def new_controller_thread # :nodoc: + yield + end + end + # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1. # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: |