Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actionpack/lib/action_dispatch/middleware/executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def initialize(app, executor)
end

def call(env)
@executor.instance_variable_set('@rack_test', env['rack.test'])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this hack is to be replaced with #24608 or similiar

state = @executor.run!
begin
response = @app.call(env)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ def checkout(checkout_timeout = @checkout_timeout)
# +conn+: an AbstractAdapter object, which was obtained by earlier by
# calling #checkout on this pool.
def checkin(conn)
conn.assert_connection_reuseable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connections may be checked in (closed) in a dirty state. We should rollback on checkin to close dangling open transactions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are resetting connections, but only when they're reaped from the pool.

The pg adapter properly resets the conn in-place (cc0d54b) but mysql2 adapter aliases it to a reconnect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed this only happens in case of progammer error and the earlier we blow up the better.

I can think of 2 cases this can happen:

  1. Calling checkin inside a transaction:

    Post.transaction do
        ...
        AR::connection.close # calls poll.checkin connection
    end # [*]
  2. incorrect transaction use, not comiting or rollingback called after begin_transaction

       AR::Base.connection.begin_transaction
       ...
       AR::Base.connection.close

If we roll back on case 1 it'll blow up when the transaction block original tries to release the transaction at [*], as it's already released. That should do as well, but will be a bit harder to see that where the error is coming from.
In case 2 rolling back automatically kind of silents the issue. So maybe it's a good idea to add an error log as well.

In both case i think the last thing we want is the current behaviour of happily allowing reusing the connection with an opened transaction.

I guess mysql close and reconnect should be fine as we'd be calling reset! only if we have open transactions.

synchronize do
remove_connection_from_thread_cache conn

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ def verify!(*ignored)
reconnect! unless active?
end

# raises if there are opened transactions on the connection preventing reuse of connection.
def assert_connection_reuseable
if transaction_open?
raise ActiveRecordError, "Attempt to reuse a connection with open transaction"
end
end

# Provides access to the underlying database driver for this adapter. For
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
# and a PGconn object in case of PostgreSQLAdapter.
Expand Down
4 changes: 2 additions & 2 deletions activerecord/lib/active_record/query_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def self.install_executor_hooks(executor = ActiveSupport::Executor)
executor.register_hook(self)

executor.to_complete do
# FIXME: This should be skipped when env['rack.test']
ActiveRecord::Base.clear_active_connections!
is_test = self.class.instance_variable_get("@rack_test")
ActiveRecord::Base.clear_active_connections! unless is_test
end
end
end
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/cases/adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def test_show_nonexistent_variable_returns_nil
end

def test_not_specifying_database_name_for_cross_database_selects
teardown_fixtures
begin
assert_nothing_raised do
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ def execute(sql, name = nil) return sql end
end
end

teardown do
def teardown
ActiveRecord::Base.connection.singleton_class.class_eval do
remove_method :execute
alias_method :execute, :execute_without_stub
remove_method :execute_without_stub
end
reset_connection
end

Expand Down
4 changes: 4 additions & 0 deletions activerecord/test/cases/adapters/mysql2/connection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,23 @@ def test_mysql_sql_mode_variable_overrides_strict_mode
end

def test_passing_arbitary_flags_to_adapter
teardown_fixtures
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS}))
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end

def test_passing_flags_by_array_to_adapter
teardown_fixtures
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] }))
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end

def test_mysql_set_session_variable
teardown_fixtures
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
Expand All @@ -121,6 +124,7 @@ def test_mysql_set_session_variable
end

def test_mysql_set_session_variable_to_default
teardown_fixtures
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class NonExistentTable < ActiveRecord::Base

def setup
super
teardown_fixtures
@subscriber = SQLSubscriber.new
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/cases/base_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,7 @@ def test_marshalling_with_associations

if Process.respond_to?(:fork) && !in_memory_db?
def test_marshal_between_processes
teardown_fixtures
# Define a new model to ensure there are no caches
if self.class.const_defined?("Post", false)
flunk "there should be no post constant"
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/cases/connection_management_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def call(env)
end

def setup
teardown_fixtures
@env = {}
@app = App.new
@management = middleware(@app)
Expand Down
5 changes: 5 additions & 0 deletions activerecord/test/cases/query_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts

setup do
teardown_fixtures
end

teardown do
Task.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache!
Expand Down Expand Up @@ -211,6 +215,7 @@ def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
private
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
executor.instance_variable_set('@rack_test', true)
ActiveRecord::QueryCache.install_executor_hooks executor
lambda { |env| executor.wrap { app.call(env) } }
end
Expand Down
2 changes: 2 additions & 0 deletions activerecord/test/support/connection_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module ConnectionHelper
def run_without_connection
teardown_fixtures
original_connection = ActiveRecord::Base.remove_connection
yield original_connection
ensure
Expand All @@ -8,6 +9,7 @@ def run_without_connection

# Used to drop all cache query plans in tests.
def reset_connection
teardown_fixtures
original_connection = ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(original_connection)
end
Expand Down