diff --git a/lib/cypress-rails/launches_cypress.rb b/lib/cypress-rails/launches_cypress.rb index 1ee6bc4..b34714c 100644 --- a/lib/cypress-rails/launches_cypress.rb +++ b/lib/cypress-rails/launches_cypress.rb @@ -2,13 +2,14 @@ require_relative "config" require_relative "initializer_hooks" require_relative "manages_transactions" +require_relative "manages_transactions_before_rails72" require_relative "starts_rails_server" module CypressRails class LaunchesCypress def initialize @initializer_hooks = InitializerHooks.instance - @manages_transactions = ManagesTransactions.instance + @manages_transactions = manages_transactions_instance @starts_rails_server = StartsRailsServer.new @finds_bin = FindsBin.new end @@ -38,6 +39,14 @@ def call(command, config) private + def manages_transactions_instance + if Gem::Version.new(Rails.version) >= Gem::Version.new("7.2") + ManagesTransactions.instance + else + ManagesTransactionsBeforeRails72.instance + end + end + def set_exit_hooks!(config) at_exit do run_exit_hooks_if_necessary!(config) diff --git a/lib/cypress-rails/manages_transactions.rb b/lib/cypress-rails/manages_transactions.rb index 484c434..48f8a8a 100644 --- a/lib/cypress-rails/manages_transactions.rb +++ b/lib/cypress-rails/manages_transactions.rb @@ -7,44 +7,44 @@ def self.instance end def begin_transaction - @connections = gather_connections - @connections.each do |connection| - connection.begin_transaction joinable: false, _lazy: false - connection.pool.lock_thread = true + setup_shared_connection_pool + + # Begin transactions for connections already established + @connection_pools = ActiveRecord::Base.connection_handler.connection_pools(:writing) + @connection_pools.each do |pool| + pool.pin_connection!(true) + pool.lease_connection end # When connections are established in the future, begin a transaction too - @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") { |_, _, _, _, payload| + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| if payload.key?(:spec_name) && (spec_name = payload[:spec_name]) - setup_shared_connection_pool - - begin - connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) - rescue ActiveRecord::ConnectionNotEstablished - connection = nil - end - if connection && !@connections.include?(connection) - connection.begin_transaction joinable: false, _lazy: false - connection.pool.lock_thread = true - @connections << connection + if spec_name + pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(spec_name) + if pool + setup_shared_connection_pool + + unless @connection_pools.include?(pool) + pool.pin_connection!(true) + pool.lease_connection + @connection_pools << pool + end + end end end - } + end @initializer_hooks.run(:after_transaction_start) end def rollback_transaction - return unless @connections.present? - + ActiveRecord::Base.asynchronous_queries_tracker.finalize_session ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber - @connections.each do |connection| - connection.rollback_transaction if connection.transaction_open? - connection.pool.lock_thread = false - end - @connections.clear + return unless @connection_pools.any?(&:active_connection?) + @connection_pools.map(&:unpin_connection!) + @connection_pools.clear ActiveRecord::Base.connection_handler.clear_active_connections! end diff --git a/lib/cypress-rails/manages_transactions_before_rails72.rb b/lib/cypress-rails/manages_transactions_before_rails72.rb new file mode 100644 index 0000000..ddd6573 --- /dev/null +++ b/lib/cypress-rails/manages_transactions_before_rails72.rb @@ -0,0 +1,78 @@ +require_relative "initializer_hooks" + +module CypressRails + class ManagesTransactionsBeforeRails72 + def self.instance + @instance ||= new + end + + def begin_transaction + @connections = gather_connections + @connections.each do |connection| + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") { |_, _, _, _, payload| + if payload.key?(:spec_name) && (spec_name = payload[:spec_name]) + setup_shared_connection_pool + + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) + rescue ActiveRecord::ConnectionNotEstablished + connection = nil + end + + if connection && !@connections.include?(connection) + connection.begin_transaction joinable: false, _lazy: false + connection.pool.lock_thread = true + @connections << connection + end + end + } + + @initializer_hooks.run(:after_transaction_start) + end + + def rollback_transaction + return unless @connections.present? + + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + + @connections.each do |connection| + connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false + end + @connections.clear + + ActiveRecord::Base.connection_handler.clear_active_connections! + end + + private + + def initialize + @initializer_hooks = InitializerHooks.instance + end + + def gather_connections + setup_shared_connection_pool + + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) + end + + # Shares the writing connection pool with connections on + # other handlers. + # + # In an application with a primary and replica the test fixtures + # need to share a connection pool so that the reading connection + # can see data in the open transaction on the writing connection. + def setup_shared_connection_pool + return unless ActiveRecord::TestFixtures.respond_to?(:setup_shared_connection_pool) + @legacy_saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} } + @saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} } + + ActiveRecord::TestFixtures.instance_method(:setup_shared_connection_pool).bind(self).call + end + end +end diff --git a/lib/cypress-rails/resets_state.rb b/lib/cypress-rails/resets_state.rb index 620b82b..01181d8 100644 --- a/lib/cypress-rails/resets_state.rb +++ b/lib/cypress-rails/resets_state.rb @@ -1,11 +1,12 @@ require_relative "config" require_relative "manages_transactions" +require_relative "manages_transactions_before_rails72" require_relative "initializer_hooks" module CypressRails class ResetsState def initialize - @manages_transactions = ManagesTransactions.instance + @manages_transactions = manages_transactions_instance @initializer_hooks = InitializerHooks.instance end @@ -16,5 +17,15 @@ def call(transactional_server:) end @initializer_hooks.run(:after_state_reset) end + + private + + def manages_transactions_instance + if Gem::Version.new(Rails.version) >= Gem::Version.new("7.2") + ManagesTransactions.instance + else + ManagesTransactionsBeforeRails72.instance + end + end end end