From 7b54cd424477c56d3d6b8f3ad983d8ffabe32554 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 30 Jul 2025 14:39:18 -0700 Subject: [PATCH 01/26] allow rails 7.1 --- Appraisals | 2 +- Gemfile.lock | 2 +- fibered_mysql2.gemspec | 2 +- gemfiles/{rails_6_1.gemfile => rails_7_1.gemfile} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename gemfiles/{rails_6_1.gemfile => rails_7_1.gemfile} (91%) diff --git a/Appraisals b/Appraisals index 3ff90ab..70e2a93 100644 --- a/Appraisals +++ b/Appraisals @@ -2,4 +2,4 @@ require "appraisal/matrix" -appraisal_matrix(rails: [">= 6.1", "< 7.1"]) +appraisal_matrix(rails: [">= 7.0", "< 7.2"]) diff --git a/Gemfile.lock b/Gemfile.lock index a068550..3a35223 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ PATH specs: fibered_mysql2 (0.3.1) em-synchrony (~> 1.0) - rails (>= 6.1, < 7.1) + rails (>= 7.0, < 7.2) GEM remote: https://rubygems.org/ diff --git a/fibered_mysql2.gemspec b/fibered_mysql2.gemspec index 8f97bbf..619719f 100644 --- a/fibered_mysql2.gemspec +++ b/fibered_mysql2.gemspec @@ -30,5 +30,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency 'em-synchrony', '~> 1.0' - spec.add_dependency 'rails', '>= 6.1', '< 7.1' + spec.add_dependency 'rails', '>= 7.0', '< 7.2' end diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_7_1.gemfile similarity index 91% rename from gemfiles/rails_6_1.gemfile rename to gemfiles/rails_7_1.gemfile index 95aa0d4..5b08fd6 100644 --- a/gemfiles/rails_6_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -11,6 +11,6 @@ gem "pry" gem "pry-byebug" gem "rake" gem "rspec" -gem "rails", "~> 6.1.0" +gem "rails", "~> 7.1.0" gemspec path: "../" From f0f958e5a4c676b93e3f8f9b6de96bbac3d9725b Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 09:32:54 -0700 Subject: [PATCH 02/26] segfaulting for some reason --- .ruby-version | 2 +- Gemfile.lock | 12 +- .../fibered_mysql2_adapter.rb | 9 +- .../fibered_database_connection_pool.rb | 80 ++++----- .../fibered_mysql2_connection_factory.rb | 167 ++++++++++-------- spec/spec_helper.rb | 8 +- .../fibered_database_connection_pool_spec.rb | 28 ++- 7 files changed, 163 insertions(+), 143 deletions(-) diff --git a/.ruby-version b/.ruby-version index 9cec716..37d02a6 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.6 +3.3.8 diff --git a/Gemfile.lock b/Gemfile.lock index 3a35223..62d956b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,7 +92,7 @@ GEM tins (~> 1.6) crass (1.0.6) date (3.3.4) - diff-lcs (1.5.1) + diff-lcs (1.6.2) docile (1.4.1) em-synchrony (1.0.6) eventmachine (>= 1.0.0.beta.1) @@ -169,19 +169,19 @@ GEM thor (~> 1.0) zeitwerk (~> 2.5) rake (13.2.1) - rspec (3.13.0) + rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.5) rspec-support (~> 3.13.0) - rspec-expectations (3.13.1) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.1) + rspec-support (3.13.4) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) diff --git a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb index 06383b2..e0ee53e 100644 --- a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb @@ -3,8 +3,11 @@ require 'em-synchrony' require 'active_model' require 'active_record/errors' + require 'active_record/connection_adapters/mysql2_adapter' -require 'active_record/connection_adapters/em_mysql2_adapter' +require 'em-synchrony/mysql2' +require 'em-synchrony/activerecord' +# require 'active_record/connection_adapters/em_mysql2_adapter' module FiberedMysql2 module FiberedMysql2Adapter_5_2 @@ -62,7 +65,9 @@ def owner_fiber end end - class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::EMMysql2Adapter + class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter + require 'em-synchrony/activerecord_4_2' + include EM::Synchrony::ActiveRecord::Adapter_4_2 include FiberedMysql2Adapter_5_2 class << self diff --git a/lib/fibered_mysql2/fibered_database_connection_pool.rb b/lib/fibered_mysql2/fibered_database_connection_pool.rb index 4c49a14..71af581 100644 --- a/lib/fibered_mysql2/fibered_database_connection_pool.rb +++ b/lib/fibered_mysql2/fibered_database_connection_pool.rb @@ -184,33 +184,30 @@ def mon_exit_for_cond end module FiberedDatabaseConnectionPool - include FiberedMonitorMixin - - module Adapter_5_2 - def cached_connections - @thread_cached_conns - end - - def current_connection_id - connection_cache_key(current_thread) + module Adapter_7_0 + def release_connection(owner_thread = Fiber.current) + if (conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))) + checkin(conn) + end end - def checkout(checkout_timeout = @checkout_timeout) - begin - reap_connections - rescue => ex - ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}") + def with_connection + unless (conn = cached_connections[current_connection_id]) # Invoca Patch to use Fiber + conn = connection + fresh_connection = true end - super + yield conn + ensure + release_connection if fresh_connection end - def release_connection(owner_thread = Fiber.current) - if (conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))) - checkin(conn) - end + # Not needed in Rails 7.1 and later + def current_thread + Fiber.current end end - include Adapter_5_2 + include Adapter_7_0 if ::ActiveRecord.gem_version < "7.1" + include FiberedMonitorMixin # This is switches the connection pool's mutex and condition variables to event machine / Fiber compatible ones. def initialize(pool_config) if pool_config.db_config.reaping_frequency @@ -222,22 +219,25 @@ def initialize(pool_config) @reaper = nil # no need to keep a reference to this since it does nothing in this sub-class end - def connection - # this is correctly done double-checked locking - # (ThreadSafe::Cache's lookups have volatile semantics) - if (result = cached_connections[current_connection_id]) - result - else - synchronize do - if (result = cached_connections[current_connection_id]) - result - else - cached_connections[current_connection_id] = checkout - end - end + def current_connection_id + connection_cache_key(current_thread) + end + + def cached_connections + @thread_cached_conns + end + + # Invoca patch that reaps orphaned connections on checkout. This reduces the number of connections left open by dead fibers. + def checkout(checkout_timeout = @checkout_timeout) + begin + reap_connections + rescue => ex + ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}") end + super end + # Invoca specific helper method to reap connections on checkout def reap_connections cached_connections.values.each do |connection| unless connection.owner.alive? @@ -246,17 +246,9 @@ def reap_connections end end - private - - #-- - # This hook-in method allows for easier monkey-patching fixes needed by - # JRuby users that use Fibers. - def connection_cache_key(fiber) - fiber - end - - def current_thread - Fiber.current + # Overrides EM::Synchrony connection method. + def connection + cached_connections[current_connection_id] ||= checkout end end end diff --git a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb index fb24cb9..6b5913a 100644 --- a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb +++ b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb @@ -5,91 +5,118 @@ module EM::Synchrony module ActiveRecord _ = Adapter_4_2 - module Adapter_4_2 - def configure_connection - super # undo EM::Synchrony's override here - end + if ::ActiveRecord.gem_version < "7.1" + module Adapter_4_2 + def configure_connection + super # undo EM::Synchrony's override here + end - def transaction(*args) - super # and here - end + def transaction(*args) + super # and here + end + + EMTransactionManager = TransactionManager + class TransactionManager < EMTransactionManager + # Overriding the em-synchrony override to bring it up to rails 6 requirements. + # Changes from the original Rails 6 source are: + # 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable + # 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name + # + # Original EM Synchrony Source: + # https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44 + # + # Original Rails Source: + # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224 - _ = TransactionManager - class TransactionManager < _ - # Overriding the em-synchrony override to bring it up to rails 6 requirements. - # Changes from the original Rails 6 source are: - # 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable - # 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name - # - # Original EM Synchrony Source: - # https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44 - # - # Original Rails Source: - # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224 - def begin_transaction(isolation: nil, joinable: true, _lazy: true) - @connection.lock.synchronize do - run_commit_callbacks = !current_transaction.joinable? - transaction = + def begin_transaction(isolation: nil, joinable: true, _lazy: true) + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + + transaction = if _current_stack.empty? - ::ActiveRecord::ConnectionAdapters::RealTransaction.new(@connection, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks) + ::ActiveRecord::ConnectionAdapters::RealTransaction.new( + @connection, + isolation:, + joinable:, + run_commit_callbacks: + ) + elsif ActiveRecord.gem_version >= "7.1" && current_transaction.restartable? + ::ActiveRecord::ConnectionAdapters::RestartParentTransaction.new( + @connection, + current_transaction, + isolation:, + joinable:, + run_commit_callbacks: + ) + else + ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new( + @connection, + "active_record_#{Fiber.current.object_id}_#{open_transactions}", + _current_stack.last, + isolation:, + joinable:, + run_commit_callbacks: + ) + end + + if ::ActiveRecord.gem_version < "7.1" || !transaction.materialized? + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true else - ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new(@connection, "active_record_#{Fiber.current.object_id}_#{open_transactions}", _current_stack.last, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks) + transaction.materialize! end + end - if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy - @has_unmaterialized_transactions = true - else - transaction.materialize! + _current_stack.push(transaction) + transaction end - _current_stack.push(transaction) - transaction end - end - # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def materialize_transactions - return if @materializing_transactions - return unless @has_unmaterialized_transactions - - @connection.lock.synchronize do - begin - @materializing_transactions = true - _current_stack.each { |t| t.materialize! unless t.materialized? } - ensure - @materializing_transactions = false + # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def materialize_transactions + return if @materializing_transactions + return unless @has_unmaterialized_transactions + + @connection.lock.synchronize do + begin + @materializing_transactions = true + _current_stack.each { |t| t.materialize! unless t.materialized? } + ensure + @materializing_transactions = false + end + @has_unmaterialized_transactions = false end - @has_unmaterialized_transactions = false end - end - # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def commit_transaction - @connection.lock.synchronize do - transaction = _current_stack.last - - begin - transaction.before_commit_records - ensure - _current_stack.pop - end + # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def commit_transaction + @connection.lock.synchronize do + transaction = _current_stack.last - transaction.commit - transaction.commit_records + begin + transaction.before_commit_records + ensure + _current_stack.pop + end + + transaction.commit + transaction.commit_records + end end - end - # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def rollback_transaction(transaction = nil) - @connection.lock.synchronize do - transaction ||= _current_stack.pop - transaction.rollback - transaction.rollback_records + # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= _current_stack.pop + transaction.rollback + transaction.rollback_records + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 690d6d4..129ea59 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'coveralls' +# require 'coveralls' -Coveralls.wear! +# Coveralls.wear! require 'bundler/setup' require 'rails' @@ -24,4 +24,8 @@ config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end + + config.before(:all) do + ActiveSupport::IsolatedExecutionState.isolation_level = :fiber + end end diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index 0c6766f..06e3f3d 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -377,7 +377,7 @@ def cancel_timer(timer_block) allow(client).to receive(:close) allow(client).to receive(:info).and_return({ version: "5.7.27" }) allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) - allow(Mysql2::EM::Client).to receive(:new) { |config| client } + allow(Mysql2::EM::Client).to receive(:new) { client } establish_connection end @@ -392,22 +392,20 @@ def cancel_timer(timer_block) context "with more than 1 connection in the pool" do it "should serve separate connections per fiber" do - expected_query = if Rails::VERSION::MAJOR > 4 - "SET @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483" - else - "SET @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483, @@SESSION.sql_mode = 'STRICT_ALL_TABLES'" - end + expected_query = "SET @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483" expect(client).to receive(:query) do |*args| expect(args).to eq([expected_query]) end.exactly(2).times c0 = ActiveRecord::Base.connection c1 = nil - fiber = Fiber.new { c1 = ActiveRecord::Base.connection } + fiber = Fiber.new do + c1 = ActiveRecord::Base.connection + end fiber.resume - expect(c0).to be - expect(c1).to be + expect(c0).to be_truthy + expect(c1).to be_truthy expect(c1).to_not eq(c0) expect(c0.owner).to eq(Fiber.current) expect(c1.owner).to eq(fiber) @@ -418,9 +416,6 @@ def cancel_timer(timer_block) it "should reclaim connections when the fiber has exited" do expect(client).to receive(:query) { }.exactly(2).times - reap_connection_count = Rails::VERSION::MAJOR > 4 ? 5 : 3 - expect(ActiveRecord::Base.connection_pool).to receive(:reap_connections).with(no_args).exactly(reap_connection_count).times.and_call_original - ActiveRecord::Base.connection c1 = nil fiber1 = Fiber.new { c1 = ActiveRecord::Base.connection } @@ -449,9 +444,6 @@ def cancel_timer(timer_block) expect(client).to receive(:query) { }.exactly(1).times EM.run do - reap_connection_count = Rails::VERSION::MAJOR > 4 ? 4 : 3 - expect(ActiveRecord::Base.connection_pool).to receive(:reap_connections).with(no_args).exactly(reap_connection_count).times.and_call_original - c0 = ActiveRecord::Base.connection connection_pool = c0.pool c1 = nil @@ -460,12 +452,12 @@ def cancel_timer(timer_block) em_helper.run_next_ticks c1 = ActiveRecord::Base.connection.tap { em_helper.run_next_ticks } end - fiber1.resume + fiber1.resume # Fiber should block because there is only one connection - expect(c1).to eq(nil) # should block because there is only one connection + expect(c1).to eq(nil) connection_pool.checkin(c0) - em_helper.run_next_ticks + em_helper.run_next_ticks # Resume fiber1 now that c0 is checked in. expect(c1).to eq(c0) From 2a3657979c7f671813a67be28c78aea67d8b0212 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 09:34:45 -0700 Subject: [PATCH 03/26] add require logger --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 129ea59..a5e1db4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ # Coveralls.wear! require 'bundler/setup' +require 'logger' require 'rails' require 'active_record' require 'fibered_mysql2' From 0a992667471f093c5d3c7263a0056cee852503b3 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 10:41:34 -0700 Subject: [PATCH 04/26] only include em synchrony adapter methods in rails 7.0 --- .ruby-version | 2 +- .../fibered_mysql2_adapter.rb | 15 +- .../fibered_database_connection_pool.rb | 11 +- .../fibered_mysql2_connection_factory.rb | 197 +++++++++--------- spec/spec_helper.rb | 1 + .../fibered_database_connection_pool_spec.rb | 21 +- spec/unit/fibered_mysql2_adapter_spec.rb | 6 +- .../fibered_mysql2_connection_factory_spec.rb | 4 + 8 files changed, 129 insertions(+), 128 deletions(-) diff --git a/.ruby-version b/.ruby-version index 37d02a6..9cec716 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.8 +3.1.6 diff --git a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb index e0ee53e..a42c8a6 100644 --- a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb @@ -66,9 +66,12 @@ def owner_fiber end class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter - require 'em-synchrony/activerecord_4_2' - include EM::Synchrony::ActiveRecord::Adapter_4_2 - include FiberedMysql2Adapter_5_2 + + if ::ActiveRecord.gem_version < "7.1" + require 'em-synchrony/activerecord_4_2' + include EM::Synchrony::ActiveRecord::Adapter_4_2 + include FiberedMysql2Adapter_5_2 + end class << self # Copied from Mysql2Adapter, except with the EM Mysql2 client @@ -83,8 +86,8 @@ def new_client(config) end end - def initialize(*args) - super - end + # def initialize(*args) + # super + # end end end diff --git a/lib/fibered_mysql2/fibered_database_connection_pool.rb b/lib/fibered_mysql2/fibered_database_connection_pool.rb index 71af581..c61f510 100644 --- a/lib/fibered_mysql2/fibered_database_connection_pool.rb +++ b/lib/fibered_mysql2/fibered_database_connection_pool.rb @@ -230,22 +230,13 @@ def cached_connections # Invoca patch that reaps orphaned connections on checkout. This reduces the number of connections left open by dead fibers. def checkout(checkout_timeout = @checkout_timeout) begin - reap_connections + reap rescue => ex ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}") end super end - # Invoca specific helper method to reap connections on checkout - def reap_connections - cached_connections.values.each do |connection| - unless connection.owner.alive? - checkin(connection) - end - end - end - # Overrides EM::Synchrony connection method. def connection cached_connections[current_connection_id] ||= checkout diff --git a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb index 6b5913a..543718e 100644 --- a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb +++ b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb @@ -1,122 +1,121 @@ # frozen_string_literal: true require_relative '../active_record/connection_adapters/fibered_mysql2_adapter' +require 'em-synchrony/activerecord_4_2' module EM::Synchrony module ActiveRecord _ = Adapter_4_2 - if ::ActiveRecord.gem_version < "7.1" - module Adapter_4_2 - def configure_connection - super # undo EM::Synchrony's override here - end + module Adapter_4_2 + def configure_connection + super # undo EM::Synchrony's override here + end - def transaction(*args) - super # and here - end + def transaction(*args) + super # and here + end - EMTransactionManager = TransactionManager - class TransactionManager < EMTransactionManager - # Overriding the em-synchrony override to bring it up to rails 6 requirements. - # Changes from the original Rails 6 source are: - # 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable - # 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name - # - # Original EM Synchrony Source: - # https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44 - # - # Original Rails Source: - # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224 - - def begin_transaction(isolation: nil, joinable: true, _lazy: true) - @connection.lock.synchronize do - run_commit_callbacks = !current_transaction.joinable? - - transaction = - if _current_stack.empty? - ::ActiveRecord::ConnectionAdapters::RealTransaction.new( - @connection, - isolation:, - joinable:, - run_commit_callbacks: - ) - elsif ActiveRecord.gem_version >= "7.1" && current_transaction.restartable? - ::ActiveRecord::ConnectionAdapters::RestartParentTransaction.new( - @connection, - current_transaction, - isolation:, - joinable:, - run_commit_callbacks: - ) - else - ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new( - @connection, - "active_record_#{Fiber.current.object_id}_#{open_transactions}", - _current_stack.last, - isolation:, - joinable:, - run_commit_callbacks: - ) - end - - if ::ActiveRecord.gem_version < "7.1" || !transaction.materialized? - if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy - @has_unmaterialized_transactions = true - else - transaction.materialize! - end + EMTransactionManager = TransactionManager + class TransactionManager < EMTransactionManager + # Overriding the em-synchrony override to bring it up to rails 6 requirements. + # Changes from the original Rails 6 source are: + # 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable + # 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name + # + # Original EM Synchrony Source: + # https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44 + # + # Original Rails Source: + # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224 + + def begin_transaction(isolation: nil, joinable: true, _lazy: true) + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + + transaction = + if _current_stack.empty? + ::ActiveRecord::ConnectionAdapters::RealTransaction.new( + @connection, + isolation:, + joinable:, + run_commit_callbacks: + ) + elsif ActiveRecord.gem_version >= "7.1" && current_transaction.restartable? + ::ActiveRecord::ConnectionAdapters::RestartParentTransaction.new( + @connection, + current_transaction, + isolation:, + joinable:, + run_commit_callbacks: + ) + else + ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new( + @connection, + "active_record_#{Fiber.current.object_id}_#{open_transactions}", + _current_stack.last, + isolation:, + joinable:, + run_commit_callbacks: + ) end - _current_stack.push(transaction) - transaction + if ::ActiveRecord.gem_version < "7.1" || !transaction.materialized? + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true + else + transaction.materialize! + end end + + _current_stack.push(transaction) + transaction end + end - # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def materialize_transactions - return if @materializing_transactions - return unless @has_unmaterialized_transactions - - @connection.lock.synchronize do - begin - @materializing_transactions = true - _current_stack.each { |t| t.materialize! unless t.materialized? } - ensure - @materializing_transactions = false - end - @has_unmaterialized_transactions = false + # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def materialize_transactions + return if @materializing_transactions + return unless @has_unmaterialized_transactions + + @connection.lock.synchronize do + begin + @materializing_transactions = true + _current_stack.each { |t| t.materialize! unless t.materialized? } + ensure + @materializing_transactions = false end + @has_unmaterialized_transactions = false end + end - # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def commit_transaction - @connection.lock.synchronize do - transaction = _current_stack.last - - begin - transaction.before_commit_records - ensure - _current_stack.pop - end - - transaction.commit - transaction.commit_records + # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def commit_transaction + @connection.lock.synchronize do + transaction = _current_stack.last + + begin + transaction.before_commit_records + ensure + _current_stack.pop end + + transaction.commit + transaction.commit_records end + end - # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def rollback_transaction(transaction = nil) - @connection.lock.synchronize do - transaction ||= _current_stack.pop - transaction.rollback - transaction.rollback_records - end + # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= _current_stack.pop + transaction.rollback + transaction.rollback_records end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a5e1db4..364d084 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,5 +28,6 @@ config.before(:all) do ActiveSupport::IsolatedExecutionState.isolation_level = :fiber + ActiveRecord::Base.logger = Logger.new("/dev/null") end end diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index 06e3f3d..b1b1375 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -375,6 +375,7 @@ def cancel_timer(timer_block) allow(client).to receive(:escape) { |query| query } allow(client).to receive(:ping) { true } allow(client).to receive(:close) + allow(client).to receive(:closed?) { false } allow(client).to receive(:info).and_return({ version: "5.7.27" }) allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) allow(Mysql2::EM::Client).to receive(:new) { client } @@ -392,10 +393,9 @@ def cancel_timer(timer_block) context "with more than 1 connection in the pool" do it "should serve separate connections per fiber" do - expected_query = "SET @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483" - expect(client).to receive(:query) do |*args| - expect(args).to eq([expected_query]) - end.exactly(2).times + # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. + allow(client).to receive(:query)#.exactly(2).times + # end c0 = ActiveRecord::Base.connection c1 = nil @@ -414,11 +414,13 @@ def cancel_timer(timer_block) end it "should reclaim connections when the fiber has exited" do - expect(client).to receive(:query) { }.exactly(2).times + # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. + allow(client).to receive(:query) + # end ActiveRecord::Base.connection c1 = nil - fiber1 = Fiber.new { c1 = ActiveRecord::Base.connection } + fiber1 = Fiber.new { c1 = ActiveRecord::Base.connection.tap(&:verify!) } # Force configuring the raw mysql client. c2 = nil fiber2 = Fiber.new { c2 = ActiveRecord::Base.connection } @@ -441,7 +443,9 @@ def cancel_timer(timer_block) end it "should hand off connection on checkin to any fiber waiting on checkout" do - expect(client).to receive(:query) { }.exactly(1).times + # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. + allow(client).to receive(:query) + # end EM.run do c0 = ActiveRecord::Base.connection @@ -449,10 +453,9 @@ def cancel_timer(timer_block) c1 = nil fiber1 = Fiber.new do - em_helper.run_next_ticks c1 = ActiveRecord::Base.connection.tap { em_helper.run_next_ticks } end - fiber1.resume # Fiber should block because there is only one connection + fiber1.resume # Fiber should block because there the connection pool has no connections available. expect(c1).to eq(nil) diff --git a/spec/unit/fibered_mysql2_adapter_spec.rb b/spec/unit/fibered_mysql2_adapter_spec.rb index cc2ac98..1ab1c6e 100644 --- a/spec/unit/fibered_mysql2_adapter_spec.rb +++ b/spec/unit/fibered_mysql2_adapter_spec.rb @@ -58,7 +58,7 @@ end end - context '#lease' do + context '#lease', if: ActiveRecord.gem_version < "7.1" do subject { adapter.lease } it { should eq(Fiber.current) } @@ -77,7 +77,7 @@ end end - context '#expire' do + context '#expire', if: ActiveRecord.gem_version < "7.1" do subject { adapter.expire } context 'if the connection is not in use' do @@ -131,7 +131,7 @@ end end - context 'other mixins' do + context 'other mixins', if: ActiveRecord.gem_version < "7.1" do it 'raises if @owner has been overwritten with a non-Fiber' do adapter.instance_variable_set(:@owner, Thread.new { }) diff --git a/spec/unit/fibered_mysql2_connection_factory_spec.rb b/spec/unit/fibered_mysql2_connection_factory_spec.rb index 38d2dd1..da3effc 100644 --- a/spec/unit/fibered_mysql2_connection_factory_spec.rb +++ b/spec/unit/fibered_mysql2_connection_factory_spec.rb @@ -16,6 +16,8 @@ allow(client).to receive(:query_options) { {} } allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) allow(client).to receive(:ping) { true } + allow(client).to receive(:close) + allow(client).to receive(:closed?) { false } allow(client).to receive(:query).and_return(stub_mysql_client_result) ActiveRecord::Base.establish_connection( :adapter => 'fibered_mysql2', @@ -40,6 +42,8 @@ allow(client).to receive(:query_options) { {} } allow(client).to receive(:escape) { |query| query } allow(client).to receive(:ping) { true } + allow(client).to receive(:close) + allow(client).to receive(:closed?) { false } allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) allow(client).to receive(:query).and_return(stub_mysql_client_result) end From d82551cdcde0e378562c5b79ef92fcd0818e16e0 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 10:42:01 -0700 Subject: [PATCH 05/26] add rails 7.1 to github workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c9a10d..d84c20c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,8 @@ jobs: ruby: [3.1, 3.2, 3.3] gemfile: - Gemfile - - gemfiles/rails_6_1.gemfile - gemfiles/rails_7_0.gemfile + - gemfiles/rails_7_1.gemfile env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} steps: From f169f301ad65b39f53ae8a400538735e640b2dee Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 10:42:26 -0700 Subject: [PATCH 06/26] add ruby 3.4 to github CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d84c20c..c8c635d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [3.1, 3.2, 3.3] + ruby: [3.1, 3.2, 3.3, 3.4] gemfile: - Gemfile - gemfiles/rails_7_0.gemfile From b2c50891f06ed59b6d5f4e450738969b809fade8 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 11:19:32 -0700 Subject: [PATCH 07/26] remove unnecessary em-synchrony code --- .../fibered_mysql2_adapter.rb | 13 +- .../fibered_database_connection_pool.rb | 7 +- .../fibered_mysql2_connection_factory.rb | 121 ------------------ 3 files changed, 7 insertions(+), 134 deletions(-) diff --git a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb index a42c8a6..0766508 100644 --- a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb @@ -6,11 +6,9 @@ require 'active_record/connection_adapters/mysql2_adapter' require 'em-synchrony/mysql2' -require 'em-synchrony/activerecord' -# require 'active_record/connection_adapters/em_mysql2_adapter' module FiberedMysql2 - module FiberedMysql2Adapter_5_2 + module FiberedMysql2Adapter_7_0 def lease if in_use? msg = "Cannot lease connection, ".dup @@ -66,11 +64,8 @@ def owner_fiber end class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter - if ::ActiveRecord.gem_version < "7.1" - require 'em-synchrony/activerecord_4_2' - include EM::Synchrony::ActiveRecord::Adapter_4_2 - include FiberedMysql2Adapter_5_2 + include FiberedMysql2Adapter_7_0 end class << self @@ -85,9 +80,5 @@ def new_client(config) end end end - - # def initialize(*args) - # super - # end end end diff --git a/lib/fibered_mysql2/fibered_database_connection_pool.rb b/lib/fibered_mysql2/fibered_database_connection_pool.rb index c61f510..f792cff 100644 --- a/lib/fibered_mysql2/fibered_database_connection_pool.rb +++ b/lib/fibered_mysql2/fibered_database_connection_pool.rb @@ -206,7 +206,9 @@ def current_thread Fiber.current end end - include Adapter_7_0 if ::ActiveRecord.gem_version < "7.1" + if ::ActiveRecord.gem_version < "7.1" + include Adapter_7_0 + end include FiberedMonitorMixin # This is switches the connection pool's mutex and condition variables to event machine / Fiber compatible ones. def initialize(pool_config) @@ -227,7 +229,8 @@ def cached_connections @thread_cached_conns end - # Invoca patch that reaps orphaned connections on checkout. This reduces the number of connections left open by dead fibers. + # Invoca patch that reaps orphaned connections on checkout. This lets us immediately use a connection left open by dead fibers + # instead of waiting for all connections to be used in the pool before they are reaped. def checkout(checkout_timeout = @checkout_timeout) begin reap diff --git a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb index 543718e..87caddb 100644 --- a/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb +++ b/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb @@ -1,127 +1,6 @@ # frozen_string_literal: true require_relative '../active_record/connection_adapters/fibered_mysql2_adapter' -require 'em-synchrony/activerecord_4_2' - -module EM::Synchrony - module ActiveRecord - _ = Adapter_4_2 - module Adapter_4_2 - def configure_connection - super # undo EM::Synchrony's override here - end - - def transaction(*args) - super # and here - end - - EMTransactionManager = TransactionManager - class TransactionManager < EMTransactionManager - # Overriding the em-synchrony override to bring it up to rails 6 requirements. - # Changes from the original Rails 6 source are: - # 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable - # 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name - # - # Original EM Synchrony Source: - # https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44 - # - # Original Rails Source: - # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224 - - def begin_transaction(isolation: nil, joinable: true, _lazy: true) - @connection.lock.synchronize do - run_commit_callbacks = !current_transaction.joinable? - - transaction = - if _current_stack.empty? - ::ActiveRecord::ConnectionAdapters::RealTransaction.new( - @connection, - isolation:, - joinable:, - run_commit_callbacks: - ) - elsif ActiveRecord.gem_version >= "7.1" && current_transaction.restartable? - ::ActiveRecord::ConnectionAdapters::RestartParentTransaction.new( - @connection, - current_transaction, - isolation:, - joinable:, - run_commit_callbacks: - ) - else - ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new( - @connection, - "active_record_#{Fiber.current.object_id}_#{open_transactions}", - _current_stack.last, - isolation:, - joinable:, - run_commit_callbacks: - ) - end - - if ::ActiveRecord.gem_version < "7.1" || !transaction.materialized? - if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy - @has_unmaterialized_transactions = true - else - transaction.materialize! - end - end - - _current_stack.push(transaction) - transaction - end - end - - # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def materialize_transactions - return if @materializing_transactions - return unless @has_unmaterialized_transactions - - @connection.lock.synchronize do - begin - @materializing_transactions = true - _current_stack.each { |t| t.materialize! unless t.materialized? } - ensure - @materializing_transactions = false - end - @has_unmaterialized_transactions = false - end - end - - # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def commit_transaction - @connection.lock.synchronize do - transaction = _current_stack.last - - begin - transaction.before_commit_records - ensure - _current_stack.pop - end - - transaction.commit - transaction.commit_records - end - end - - # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use - # fiber safe the _current_stack instead of the @stack instance variable. when marterializing - # transactions. - def rollback_transaction(transaction = nil) - @connection.lock.synchronize do - transaction ||= _current_stack.pop - transaction.rollback - transaction.rollback_records - end - end - end - end - end -end module FiberedMysql2 module FiberedMysql2ConnectionFactory From ea193b95a830a10375e55d0971097423f9aa54ad Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 11:26:52 -0700 Subject: [PATCH 08/26] remove patch for rails 7.1 --- .../fibered_database_connection_pool.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/fibered_mysql2/fibered_database_connection_pool.rb b/lib/fibered_mysql2/fibered_database_connection_pool.rb index f792cff..ba0a947 100644 --- a/lib/fibered_mysql2/fibered_database_connection_pool.rb +++ b/lib/fibered_mysql2/fibered_database_connection_pool.rb @@ -201,11 +201,15 @@ def with_connection release_connection if fresh_connection end - # Not needed in Rails 7.1 and later def current_thread Fiber.current end + + def connection + cached_connections[current_connection_id] ||= checkout + end end + if ::ActiveRecord.gem_version < "7.1" include Adapter_7_0 end @@ -239,11 +243,6 @@ def checkout(checkout_timeout = @checkout_timeout) end super end - - # Overrides EM::Synchrony connection method. - def connection - cached_connections[current_connection_id] ||= checkout - end end end From ad9fe3b31e5997b45cf66117cd221904ee4b060c Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 11:27:12 -0700 Subject: [PATCH 09/26] bump bundler version | fix rails 7.0 gemfile to add mutex_m --- Appraisals | 6 +++++- Gemfile.lock | 2 +- gemfiles/rails_7_0.gemfile | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Appraisals b/Appraisals index 70e2a93..8028e41 100644 --- a/Appraisals +++ b/Appraisals @@ -2,4 +2,8 @@ require "appraisal/matrix" -appraisal_matrix(rails: [">= 7.0", "< 7.2"]) +appraisal_matrix(rails: [">= 7.0", "< 7.2"]) do |rails:| + if rails < "7.1" + gem "mutex_m" + end +end diff --git a/Gemfile.lock b/Gemfile.lock index 62d956b..0cc4f23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -218,4 +218,4 @@ DEPENDENCIES rspec BUNDLED WITH - 2.2.29 + 2.6.9 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index 5fd8832..2e37245 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -12,5 +12,6 @@ gem "pry-byebug" gem "rake" gem "rspec" gem "rails", "~> 7.0.0" +gem "mutex_m" gemspec path: "../" From fa7924a5792780d0367be33e84ce41ee1830b143 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:04:21 -0700 Subject: [PATCH 10/26] clean up comments in cp spec --- spec/spec_helper.rb | 4 ++-- spec/unit/fibered_database_connection_pool_spec.rb | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 364d084..4b4bb0b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -# require 'coveralls' +require 'coveralls' -# Coveralls.wear! +Coveralls.wear! require 'bundler/setup' require 'logger' diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index b1b1375..d62e688 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -393,9 +393,7 @@ def cancel_timer(timer_block) context "with more than 1 connection in the pool" do it "should serve separate connections per fiber" do - # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. - allow(client).to receive(:query)#.exactly(2).times - # end + allow(client).to receive(:query) c0 = ActiveRecord::Base.connection c1 = nil @@ -409,14 +407,10 @@ def cancel_timer(timer_block) expect(c1).to_not eq(c0) expect(c0.owner).to eq(Fiber.current) expect(c1.owner).to eq(fiber) - expect(c0.in_use?).to be - expect(c1.in_use?).to be end it "should reclaim connections when the fiber has exited" do - # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. allow(client).to receive(:query) - # end ActiveRecord::Base.connection c1 = nil @@ -443,9 +437,7 @@ def cancel_timer(timer_block) end it "should hand off connection on checkin to any fiber waiting on checkout" do - # if ActiveRecord.gem_version < "7.2" # Rails 7.1 doesn't configure the raw mysql client on initialize anymore. allow(client).to receive(:query) - # end EM.run do c0 = ActiveRecord::Base.connection From 89ed20d70e024e548d5e7d869b53ec27f8cf69ca Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:06:18 -0700 Subject: [PATCH 11/26] move query stub to before block --- spec/unit/fibered_database_connection_pool_spec.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index d62e688..f572eb5 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -374,6 +374,7 @@ def cancel_timer(timer_block) allow(client).to receive(:query_options) { {} } allow(client).to receive(:escape) { |query| query } allow(client).to receive(:ping) { true } + allow(client).to receive(:query) allow(client).to receive(:close) allow(client).to receive(:closed?) { false } allow(client).to receive(:info).and_return({ version: "5.7.27" }) @@ -393,8 +394,6 @@ def cancel_timer(timer_block) context "with more than 1 connection in the pool" do it "should serve separate connections per fiber" do - allow(client).to receive(:query) - c0 = ActiveRecord::Base.connection c1 = nil fiber = Fiber.new do @@ -410,8 +409,6 @@ def cancel_timer(timer_block) end it "should reclaim connections when the fiber has exited" do - allow(client).to receive(:query) - ActiveRecord::Base.connection c1 = nil fiber1 = Fiber.new { c1 = ActiveRecord::Base.connection.tap(&:verify!) } # Force configuring the raw mysql client. @@ -437,8 +434,6 @@ def cancel_timer(timer_block) end it "should hand off connection on checkin to any fiber waiting on checkout" do - allow(client).to receive(:query) - EM.run do c0 = ActiveRecord::Base.connection connection_pool = c0.pool From 6c6f85c9f1c69537b85be5d016401e76f959819e Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:14:36 -0700 Subject: [PATCH 12/26] update to use coverall_reborn gem as coveralls was no longer maintained --- Gemfile | 2 +- Gemfile.lock | 249 +++++++++++------- gemfiles/rails_7_0.gemfile | 2 +- gemfiles/rails_7_1.gemfile | 2 +- spec/spec_helper.rb | 2 +- .../fibered_database_connection_pool_spec.rb | 2 +- 6 files changed, 152 insertions(+), 107 deletions(-) diff --git a/Gemfile b/Gemfile index 6382727..4eeab97 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gemspec gem 'appraisal' gem 'appraisal-matrix' -gem 'coveralls', require: false +gem 'coveralls_reborn', require: false gem 'mysql2', '~> 0.5' gem 'nokogiri' gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index 0cc4f23..dedb220 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,70 +8,82 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.6) - actionpack (= 7.0.8.6) - activesupport (= 7.0.8.6) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.6) - actionpack (= 7.0.8.6) - activejob (= 7.0.8.6) - activerecord (= 7.0.8.6) - activestorage (= 7.0.8.6) - activesupport (= 7.0.8.6) + zeitwerk (~> 2.6) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.6) - actionpack (= 7.0.8.6) - actionview (= 7.0.8.6) - activejob (= 7.0.8.6) - activesupport (= 7.0.8.6) + actionmailer (7.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8.6) - actionview (= 7.0.8.6) - activesupport (= 7.0.8.6) - rack (~> 2.0, >= 2.2.4) + rails-dom-testing (~> 2.2) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.6) - actionpack (= 7.0.8.6) - activerecord (= 7.0.8.6) - activestorage (= 7.0.8.6) - activesupport (= 7.0.8.6) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.6) - activesupport (= 7.0.8.6) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8.6) - activesupport (= 7.0.8.6) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) - activemodel (7.0.8.6) - activesupport (= 7.0.8.6) - activerecord (7.0.8.6) - activemodel (= 7.0.8.6) - activesupport (= 7.0.8.6) - activestorage (7.0.8.6) - actionpack (= 7.0.8.6) - activejob (= 7.0.8.6) - activerecord (= 7.0.8.6) - activesupport (= 7.0.8.6) + activemodel (7.1.5.1) + activesupport (= 7.1.5.1) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) + timeout (>= 0.4.0) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8.6) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) appraisal (2.5.0) bundler @@ -79,31 +91,42 @@ GEM thor (>= 0.14.0) appraisal-matrix (0.3.0) appraisal (~> 2.2) - bigdecimal (3.1.8) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.2) builder (3.3.0) - byebug (11.1.3) + byebug (12.0.0) + cgi (0.5.0) coderay (1.1.3) - concurrent-ruby (1.3.4) - coveralls (0.8.23) - json (>= 1.8, < 3) - simplecov (~> 0.16.1) - term-ansicolor (~> 1.3) - thor (>= 0.19.4, < 2.0) - tins (~> 1.6) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + coveralls_reborn (0.29.0) + simplecov (~> 0.22.0) + term-ansicolor (~> 1.7) + thor (~> 1.2) + tins (~> 1.32) crass (1.0.6) - date (3.3.4) + date (3.4.1) diff-lcs (1.6.2) docile (1.4.1) + drb (2.2.3) em-synchrony (1.0.6) eventmachine (>= 1.0.0.beta.1) - erubi (1.13.0) + erb (4.0.4) + cgi (>= 0.3.3) + erubi (1.13.1) eventmachine (1.2.7) globalid (1.2.1) activesupport (>= 6.1) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.7.2) - loofah (2.23.1) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + logger (1.7.0) + loofah (2.24.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -114,61 +137,79 @@ GEM marcel (1.0.4) method_source (1.1.0) mini_mime (1.1.5) - mini_portile2 (2.8.7) - minitest (5.25.1) + mini_portile2 (2.8.9) + minitest (5.25.5) + mutex_m (0.3.0) mysql2 (0.5.6) - net-imap (0.5.0) + net-imap (0.5.9) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.16.7) + nokogiri (1.18.9) mini_portile2 (~> 2.8.2) racc (~> 1.4) - pry (0.14.2) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.10.1) - byebug (~> 11.0) - pry (>= 0.13, < 0.15) + pry-byebug (3.11.0) + byebug (~> 12.0) + pry (>= 0.13, < 0.16) + psych (5.2.6) + date + stringio racc (1.8.1) - rack (2.2.10) - rack-test (2.1.0) + rack (3.2.0) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) rack (>= 1.3) - rails (7.0.8.6) - actioncable (= 7.0.8.6) - actionmailbox (= 7.0.8.6) - actionmailer (= 7.0.8.6) - actionpack (= 7.0.8.6) - actiontext (= 7.0.8.6) - actionview (= 7.0.8.6) - activejob (= 7.0.8.6) - activemodel (= 7.0.8.6) - activerecord (= 7.0.8.6) - activestorage (= 7.0.8.6) - activesupport (= 7.0.8.6) + rackup (2.2.1) + rack (>= 3) + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 7.0.8.6) - rails-dom-testing (2.2.0) + railties (= 7.1.5.1) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) - railties (7.0.8.6) - actionpack (= 7.0.8.6) - activesupport (= 7.0.8.6) - method_source + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) - rake (13.2.1) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rake (13.3.0) + rdoc (6.14.2) + erb + psych (>= 4.0.0) + reline (0.6.2) + io-console (~> 0.5) rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -182,22 +223,26 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.4) - simplecov (0.16.1) + securerandom (0.4.1) + simplecov (0.22.0) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + stringio (3.1.7) sync (0.5.0) term-ansicolor (1.11.2) tins (~> 1.0) - thor (1.3.2) - timeout (0.4.1) - tins (1.33.0) + thor (1.4.0) + timeout (0.4.3) + tins (1.39.1) bigdecimal sync tzinfo (2.0.6) concurrent-ruby (~> 1.0) - websocket-driver (0.7.6) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) zeitwerk (2.6.18) @@ -208,7 +253,7 @@ PLATFORMS DEPENDENCIES appraisal appraisal-matrix - coveralls + coveralls_reborn fibered_mysql2! mysql2 (~> 0.5) nokogiri diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index 2e37245..7fc25f1 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" gem "appraisal" gem "appraisal-matrix" -gem "coveralls", require: false +gem "coveralls_reborn", require: false gem "mysql2", "~> 0.5" gem "nokogiri" gem "pry" diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index 5b08fd6..a015c5b 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" gem "appraisal" gem "appraisal-matrix" -gem "coveralls", require: false +gem "coveralls_reborn", require: false gem "mysql2", "~> 0.5" gem "nokogiri" gem "pry" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b4bb0b..ff3b1e5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ require 'coveralls' -Coveralls.wear! +Coveralls.wear!(:rails) require 'bundler/setup' require 'logger' diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index f572eb5..78484bd 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -442,7 +442,7 @@ def cancel_timer(timer_block) fiber1 = Fiber.new do c1 = ActiveRecord::Base.connection.tap { em_helper.run_next_ticks } end - fiber1.resume # Fiber should block because there the connection pool has no connections available. + fiber1.resume # Fiber should yield back immediately because the connection pool has no connections available. expect(c1).to eq(nil) From 2e08fa686f9e047ef3e36fde7140c2f4cfa6f154 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:19:04 -0700 Subject: [PATCH 13/26] use simplecov instead of coveralls --- Appraisals | 1 + Gemfile | 2 +- Gemfile.lock | 13 +------------ gemfiles/rails_7_0.gemfile | 3 ++- gemfiles/rails_7_1.gemfile | 2 +- spec/spec_helper.rb | 5 ++--- 6 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Appraisals b/Appraisals index 8028e41..16deecc 100644 --- a/Appraisals +++ b/Appraisals @@ -5,5 +5,6 @@ require "appraisal/matrix" appraisal_matrix(rails: [">= 7.0", "< 7.2"]) do |rails:| if rails < "7.1" gem "mutex_m" + gem "base64" end end diff --git a/Gemfile b/Gemfile index 4eeab97..d8107f3 100644 --- a/Gemfile +++ b/Gemfile @@ -7,10 +7,10 @@ gemspec gem 'appraisal' gem 'appraisal-matrix' -gem 'coveralls_reborn', require: false gem 'mysql2', '~> 0.5' gem 'nokogiri' gem 'pry' gem 'pry-byebug' gem 'rake' gem 'rspec' +gem 'simplecov' diff --git a/Gemfile.lock b/Gemfile.lock index dedb220..6ab0dea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,11 +100,6 @@ GEM coderay (1.1.3) concurrent-ruby (1.3.5) connection_pool (2.5.3) - coveralls_reborn (0.29.0) - simplecov (~> 0.22.0) - term-ansicolor (~> 1.7) - thor (~> 1.2) - tins (~> 1.32) crass (1.0.6) date (3.4.1) diff-lcs (1.6.2) @@ -231,14 +226,8 @@ GEM simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) stringio (3.1.7) - sync (0.5.0) - term-ansicolor (1.11.2) - tins (~> 1.0) thor (1.4.0) timeout (0.4.3) - tins (1.39.1) - bigdecimal - sync tzinfo (2.0.6) concurrent-ruby (~> 1.0) websocket-driver (0.8.0) @@ -253,7 +242,6 @@ PLATFORMS DEPENDENCIES appraisal appraisal-matrix - coveralls_reborn fibered_mysql2! mysql2 (~> 0.5) nokogiri @@ -261,6 +249,7 @@ DEPENDENCIES pry-byebug rake rspec + simplecov BUNDLED WITH 2.6.9 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index 7fc25f1..2df8227 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -4,14 +4,15 @@ source "https://rubygems.org" gem "appraisal" gem "appraisal-matrix" -gem "coveralls_reborn", require: false gem "mysql2", "~> 0.5" gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" gem "rspec" +gem "simplecov" gem "rails", "~> 7.0.0" gem "mutex_m" +gem "base64" gemspec path: "../" diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index a015c5b..296aa8b 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -4,13 +4,13 @@ source "https://rubygems.org" gem "appraisal" gem "appraisal-matrix" -gem "coveralls_reborn", require: false gem "mysql2", "~> 0.5" gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" gem "rspec" +gem "simplecov" gem "rails", "~> 7.1.0" gemspec path: "../" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ff3b1e5..e1f305e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -require 'coveralls' - -Coveralls.wear!(:rails) +require 'simplecov' +SimpleCov.start require 'bundler/setup' require 'logger' From 4ff7d7c5df763ea3e525be4847357b13c2bf1dc8 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:23:10 -0700 Subject: [PATCH 14/26] remove simplecov --- .github/workflows/build.yml | 1 - Gemfile | 1 - Gemfile.lock | 8 -------- gemfiles/rails_7_0.gemfile | 1 - gemfiles/rails_7_1.gemfile | 1 - spec/spec_helper.rb | 3 --- 6 files changed, 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8c635d..5dce29e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,5 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - bundler: 2.2.29 bundler-cache: true - run: bundle exec rspec diff --git a/Gemfile b/Gemfile index d8107f3..103bff8 100644 --- a/Gemfile +++ b/Gemfile @@ -13,4 +13,3 @@ gem 'pry' gem 'pry-byebug' gem 'rake' gem 'rspec' -gem 'simplecov' diff --git a/Gemfile.lock b/Gemfile.lock index 6ab0dea..9cea180 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,7 +103,6 @@ GEM crass (1.0.6) date (3.4.1) diff-lcs (1.6.2) - docile (1.4.1) drb (2.2.3) em-synchrony (1.0.6) eventmachine (>= 1.0.0.beta.1) @@ -219,12 +218,6 @@ GEM rspec-support (~> 3.13.0) rspec-support (3.13.4) securerandom (0.4.1) - simplecov (0.22.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.13.2) - simplecov_json_formatter (0.1.4) stringio (3.1.7) thor (1.4.0) timeout (0.4.3) @@ -249,7 +242,6 @@ DEPENDENCIES pry-byebug rake rspec - simplecov BUNDLED WITH 2.6.9 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index 2df8227..acbfee2 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -10,7 +10,6 @@ gem "pry" gem "pry-byebug" gem "rake" gem "rspec" -gem "simplecov" gem "rails", "~> 7.0.0" gem "mutex_m" gem "base64" diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index 296aa8b..419f46a 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -10,7 +10,6 @@ gem "pry" gem "pry-byebug" gem "rake" gem "rspec" -gem "simplecov" gem "rails", "~> 7.1.0" gemspec path: "../" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e1f305e..ec3e7bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'simplecov' -SimpleCov.start - require 'bundler/setup' require 'logger' require 'rails' From 3bc863de38c50f282f072edf5392d4cd4309832c Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:28:02 -0700 Subject: [PATCH 15/26] add back in em next ticks --- spec/unit/fibered_database_connection_pool_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index 78484bd..d2a2d85 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -440,6 +440,7 @@ def cancel_timer(timer_block) c1 = nil fiber1 = Fiber.new do + em_helper.run_next_ticks c1 = ActiveRecord::Base.connection.tap { em_helper.run_next_ticks } end fiber1.resume # Fiber should yield back immediately because the connection pool has no connections available. From 7b3671d410b178a1b4742412526353b5bd2888c6 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:32:27 -0700 Subject: [PATCH 16/26] lower rspec --- .ruby-version | 2 +- Gemfile | 2 +- Gemfile.lock | 4 ++-- gemfiles/rails_7_0.gemfile | 2 +- gemfiles/rails_7_1.gemfile | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.ruby-version b/.ruby-version index 9cec716..37d02a6 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.6 +3.3.8 diff --git a/Gemfile b/Gemfile index 103bff8..946941f 100644 --- a/Gemfile +++ b/Gemfile @@ -12,4 +12,4 @@ gem 'nokogiri' gem 'pry' gem 'pry-byebug' gem 'rake' -gem 'rspec' +gem 'rspec', '< 3.13.1' diff --git a/Gemfile.lock b/Gemfile.lock index 9cea180..3eb942c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -204,7 +204,7 @@ GEM psych (>= 4.0.0) reline (0.6.2) io-console (~> 0.5) - rspec (3.13.1) + rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) @@ -241,7 +241,7 @@ DEPENDENCIES pry pry-byebug rake - rspec + rspec (< 3.13.1) BUNDLED WITH 2.6.9 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index acbfee2..cb9deed 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -9,7 +9,7 @@ gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" -gem "rspec" +gem "rspec", "< 3.13.1" gem "rails", "~> 7.0.0" gem "mutex_m" gem "base64" diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index 419f46a..195355c 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -9,7 +9,7 @@ gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" -gem "rspec" +gem "rspec", "< 3.13.1" gem "rails", "~> 7.1.0" gemspec path: "../" From cb90a785d424eb43a3a38eb2dbed0454bc59676d Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:34:27 -0700 Subject: [PATCH 17/26] lower rspec to 3.12 --- Gemfile | 2 +- Gemfile.lock | 24 ++++++++++++------------ gemfiles/rails_7_0.gemfile | 2 +- gemfiles/rails_7_1.gemfile | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 946941f..dd7ea41 100644 --- a/Gemfile +++ b/Gemfile @@ -12,4 +12,4 @@ gem 'nokogiri' gem 'pry' gem 'pry-byebug' gem 'rake' -gem 'rspec', '< 3.13.1' +gem 'rspec', '~> 3.12.0' diff --git a/Gemfile.lock b/Gemfile.lock index 3eb942c..b312044 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -204,19 +204,19 @@ GEM psych (>= 4.0.0) reline (0.6.2) io-console (~> 0.5) - rspec (3.13.0) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.5) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.3) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.4) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.5) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.7) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.4) + rspec-support (~> 3.12.0) + rspec-support (3.12.2) securerandom (0.4.1) stringio (3.1.7) thor (1.4.0) @@ -241,7 +241,7 @@ DEPENDENCIES pry pry-byebug rake - rspec (< 3.13.1) + rspec (~> 3.12.0) BUNDLED WITH 2.6.9 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index cb9deed..cafbe98 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -9,7 +9,7 @@ gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" -gem "rspec", "< 3.13.1" +gem "rspec", "~> 3.12.0" gem "rails", "~> 7.0.0" gem "mutex_m" gem "base64" diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index 195355c..8aac72f 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -9,7 +9,7 @@ gem "nokogiri" gem "pry" gem "pry-byebug" gem "rake" -gem "rspec", "< 3.13.1" +gem "rspec", "~> 3.12.0" gem "rails", "~> 7.1.0" gemspec path: "../" From 886ce61e8f2643d35d379579f8349b96beb8a0a6 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:35:30 -0700 Subject: [PATCH 18/26] add bigdecimal to rails 7.0 gemfile --- Appraisals | 1 + gemfiles/rails_7_0.gemfile | 1 + 2 files changed, 2 insertions(+) diff --git a/Appraisals b/Appraisals index 16deecc..3444d9b 100644 --- a/Appraisals +++ b/Appraisals @@ -6,5 +6,6 @@ appraisal_matrix(rails: [">= 7.0", "< 7.2"]) do |rails:| if rails < "7.1" gem "mutex_m" gem "base64" + gem "bigdecimal" end end diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index cafbe98..cb772ad 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -13,5 +13,6 @@ gem "rspec", "~> 3.12.0" gem "rails", "~> 7.0.0" gem "mutex_m" gem "base64" +gem "bigdecimal" gemspec path: "../" From 6c51dda70982c1edfd434f79fde115584eed4ea5 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:37:11 -0700 Subject: [PATCH 19/26] add comment for setting rspec to 3.12 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index dd7ea41..993cf35 100644 --- a/Gemfile +++ b/Gemfile @@ -12,4 +12,4 @@ gem 'nokogiri' gem 'pry' gem 'pry-byebug' gem 'rake' -gem 'rspec', '~> 3.12.0' +gem 'rspec', '~> 3.12.0' # Rspec 3.13 is causing segfaults for some reason in CI... From 76ddbe7bddfd75217a52b4f283ad8c8e1a9c6caf Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 12:45:51 -0700 Subject: [PATCH 20/26] bump version --- CHANGELOG.md | 17 +++++++++++++++++ Gemfile.lock | 2 +- lib/fibered_mysql2/version.rb | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ece9058..b7266cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - Unreleased +### Added +- Support for Rails 7.1 + +### Removed +- Removed support for Rails 6.1 and below. + +### Changed +#### FiberedMysql2Adapter +- Updated to no longer include EM::Synchrony::ActiveRecord::Adapter_4_2 as it is no longer necessary. +- Removed TransactionManager overrides as they are no longer necessary. + +#### FiberedMysql2::FiberedDatabaseConnectionPool +- Updated to only override methods needed in Rails 7.0. +- Removed double-checking in #connection for cached connection. +- Updated #checkout patch to use #reap instead of our custom #reaped_connections method. + ## [0.3.1] - 2024-10-30 ### Fixed - Fixed bug in FiberedMysqlAdapter.new_client that was causing `uninitialized constant` errors. diff --git a/Gemfile.lock b/Gemfile.lock index b312044..7711434 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fibered_mysql2 (0.3.1) + fibered_mysql2 (0.4.0.pre.tstarck.1) em-synchrony (~> 1.0) rails (>= 7.0, < 7.2) diff --git a/lib/fibered_mysql2/version.rb b/lib/fibered_mysql2/version.rb index b984364..ba38909 100644 --- a/lib/fibered_mysql2/version.rb +++ b/lib/fibered_mysql2/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FiberedMysql2 - VERSION = "0.3.1" + VERSION = "0.4.0.pre.tstarck.1" end From 645cbfa6a707fe8b9d740946f114eafcc1063eff Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 14:36:33 -0700 Subject: [PATCH 21/26] add back in double check on connection --- Gemfile.lock | 2 +- .../fibered_database_connection_pool.rb | 21 +++++++++++++++---- lib/fibered_mysql2/version.rb | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7711434..b2e4676 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fibered_mysql2 (0.4.0.pre.tstarck.1) + fibered_mysql2 (0.4.0.pre.tstarck.2) em-synchrony (~> 1.0) rails (>= 7.0, < 7.2) diff --git a/lib/fibered_mysql2/fibered_database_connection_pool.rb b/lib/fibered_mysql2/fibered_database_connection_pool.rb index ba0a947..18f867a 100644 --- a/lib/fibered_mysql2/fibered_database_connection_pool.rb +++ b/lib/fibered_mysql2/fibered_database_connection_pool.rb @@ -204,10 +204,6 @@ def with_connection def current_thread Fiber.current end - - def connection - cached_connections[current_connection_id] ||= checkout - end end if ::ActiveRecord.gem_version < "7.1" @@ -243,6 +239,23 @@ def checkout(checkout_timeout = @checkout_timeout) end super end + + # Invoca patch to ensure that we are using the current fiber's connection. + def connection + # this is correctly done double-checked locking + # (ThreadSafe::Cache's lookups have volatile semantics) + if (result = cached_connections[current_connection_id]) + result + else + synchronize do + if (result = cached_connections[current_connection_id]) + result + else + cached_connections[current_connection_id] = checkout + end + end + end + end end end diff --git a/lib/fibered_mysql2/version.rb b/lib/fibered_mysql2/version.rb index ba38909..f9c6d43 100644 --- a/lib/fibered_mysql2/version.rb +++ b/lib/fibered_mysql2/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FiberedMysql2 - VERSION = "0.4.0.pre.tstarck.1" + VERSION = "0.4.0.pre.tstarck.2" end From 13afedcf66c002fc40397fd7b125901012e37f4d Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 14:51:20 -0700 Subject: [PATCH 22/26] remove mocks on new --- spec/unit/fibered_database_connection_pool_spec.rb | 2 +- spec/unit/fibered_mysql2_connection_factory_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/fibered_database_connection_pool_spec.rb b/spec/unit/fibered_database_connection_pool_spec.rb index d2a2d85..2266df7 100644 --- a/spec/unit/fibered_database_connection_pool_spec.rb +++ b/spec/unit/fibered_database_connection_pool_spec.rb @@ -379,7 +379,7 @@ def cancel_timer(timer_block) allow(client).to receive(:closed?) { false } allow(client).to receive(:info).and_return({ version: "5.7.27" }) allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) - allow(Mysql2::EM::Client).to receive(:new) { client } + allow(FiberedMysql2::FiberedMysql2Adapter).to receive(:new_client) { client } establish_connection end diff --git a/spec/unit/fibered_mysql2_connection_factory_spec.rb b/spec/unit/fibered_mysql2_connection_factory_spec.rb index da3effc..9843abc 100644 --- a/spec/unit/fibered_mysql2_connection_factory_spec.rb +++ b/spec/unit/fibered_mysql2_connection_factory_spec.rb @@ -12,7 +12,7 @@ subject { ActiveRecord::Base.connection } before do - expect(Mysql2::EM::Client).to receive(:new).and_return(client) + allow(FiberedMysql2::FiberedMysql2Adapter).to receive(:new_client).and_return(client) allow(client).to receive(:query_options) { {} } allow(client).to receive(:server_info).and_return({ version: "5.7.27" }) allow(client).to receive(:ping) { true } From 05fd774dc76b65d7d1b9dc17a1fa37e0c2359ced Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 14:55:21 -0700 Subject: [PATCH 23/26] use initialize --- spec/unit/fibered_mysql2_adapter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/fibered_mysql2_adapter_spec.rb b/spec/unit/fibered_mysql2_adapter_spec.rb index 1ab1c6e..28c19a3 100644 --- a/spec/unit/fibered_mysql2_adapter_spec.rb +++ b/spec/unit/fibered_mysql2_adapter_spec.rb @@ -37,7 +37,7 @@ context "when the connection is unsuccessful" do before do - allow(Mysql2::EM::Client).to receive(:new).and_raise(Mysql2::Error.new("error", nil, error_number)) + allow_any_instance_of(Mysql2::EM::Client).to receive(:initialize).and_raise(Mysql2::Error.new("error", nil, error_number)) end context "when the error is a bad database error" do From 5acdde20260a51a2bc44fe20d9c16ad5a1a527cd Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 15:31:54 -0700 Subject: [PATCH 24/26] use separate transaction manager --- .../fibered_mysql2_adapter.rb | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb index 0766508..7c6455f 100644 --- a/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/fibered_mysql2_adapter.rb @@ -54,6 +54,98 @@ def steal! end end + def reset_transaction #:nodoc: + @transaction_manager = ::FiberedMysql2::FiberedMysql2Adapter_7_0::TransactionManager.new(self) + end + + class TransactionManager < ::ActiveRecord::ConnectionAdapters::TransactionManager + def initialize(...) + super + @stack = Hash.new { |h, k| h[k] = [] } + end + + def current_transaction #:nodoc: + _current_stack.last || ::ActiveRecord::ConnectionAdapters::TransactionManager::NULL_TRANSACTION + end + + def open_transactions + _current_stack.size + end + + def begin_transaction(isolation: nil, joinable: true, _lazy: true) + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + transaction = + if _current_stack.empty? + ::ActiveRecord::ConnectionAdapters::RealTransaction.new(@connection, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks) + else + ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new(@connection, "active_record_#{Fiber.current.object_id}_#{open_transactions}", _current_stack.last, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks) + end + + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true + else + transaction.materialize! + end + _current_stack.push(transaction) + transaction + end + end + + # Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def materialize_transactions + return if @materializing_transactions + return unless @has_unmaterialized_transactions + + @connection.lock.synchronize do + begin + @materializing_transactions = true + _current_stack.each { |t| t.materialize! unless t.materialized? } + ensure + @materializing_transactions = false + end + @has_unmaterialized_transactions = false + end + end + + # Overriding the ActiveRecord::TransactionManager#commit_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def commit_transaction + @connection.lock.synchronize do + transaction = _current_stack.last + + begin + transaction.before_commit_records + ensure + _current_stack.pop + end + + transaction.commit + transaction.commit_records + end + end + + # Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use + # fiber safe the _current_stack instead of the @stack instance variable. when marterializing + # transactions. + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= _current_stack.pop + transaction.rollback + transaction.rollback_records + end + end + + private + + def _current_stack + @stack[Fiber.current.object_id] + end + end + private def owner_fiber From 879f492fcf50dcf903d2324cf0593c94d2e1585f Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 15:33:08 -0700 Subject: [PATCH 25/26] bump version --- Gemfile.lock | 2 +- lib/fibered_mysql2/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b2e4676..a54f0a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fibered_mysql2 (0.4.0.pre.tstarck.2) + fibered_mysql2 (0.4.0.pre.tstarck.3) em-synchrony (~> 1.0) rails (>= 7.0, < 7.2) diff --git a/lib/fibered_mysql2/version.rb b/lib/fibered_mysql2/version.rb index f9c6d43..6398d9d 100644 --- a/lib/fibered_mysql2/version.rb +++ b/lib/fibered_mysql2/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FiberedMysql2 - VERSION = "0.4.0.pre.tstarck.2" + VERSION = "0.4.0.pre.tstarck.3" end From 696d2a54d4f3bd0483922414744cd7c6a9adfb38 Mon Sep 17 00:00:00 2001 From: Tristan Starck Date: Wed, 6 Aug 2025 15:49:43 -0700 Subject: [PATCH 26/26] use connect --- spec/unit/fibered_mysql2_adapter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/fibered_mysql2_adapter_spec.rb b/spec/unit/fibered_mysql2_adapter_spec.rb index 28c19a3..a93acaa 100644 --- a/spec/unit/fibered_mysql2_adapter_spec.rb +++ b/spec/unit/fibered_mysql2_adapter_spec.rb @@ -37,7 +37,7 @@ context "when the connection is unsuccessful" do before do - allow_any_instance_of(Mysql2::EM::Client).to receive(:initialize).and_raise(Mysql2::Error.new("error", nil, error_number)) + allow_any_instance_of(Mysql2::EM::Client).to receive(:connect).and_raise(Mysql2::Error.new("error", nil, error_number)) end context "when the error is a bad database error" do