From 81fc2017a90b33814aaad6494a56f076cb608388 Mon Sep 17 00:00:00 2001 From: abicky Date: Thu, 20 Jan 2022 02:05:23 +0900 Subject: [PATCH 01/30] Rescue error if user doesn't have PROCESS priviledge --- README.md | 2 + .../abstract_mysql_adapter.rb | 9 ++- .../abstract_mysql_adapter_spec.rb | 49 +++++++++---- spec/spec_helper.rb | 72 ++++++++++++------- 4 files changed, 93 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 11f6509..4254634 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 *** WE ROLL BACK TRANSACTION (2) ``` +Note that the user requires the PROCESS priviledge to collect the information. + ### ActiveRecord::ConnectionTimeoutError When `ActiveRecord::ConnectionTimeoutError` occurs, you can see the information of connection owners (threads): diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb index 9022dec..77f3f25 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb @@ -49,7 +49,14 @@ def display_processlist end def display_innodb_status_section(section_name) - status = ActiveRecord::Base.connection.execute("SHOW ENGINE INNODB STATUS").first[2] + sql = "SHOW ENGINE INNODB STATUS" + status = nil + begin + status = ActiveRecord::Base.connection.execute(sql).first[2] + rescue ActiveRecord::StatementInvalid => e + logger.error "Failed to execute '#{sql}': #{e.message}" + return + end prev_line = nil in_deadlock_section = false diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb index b91d8cc..44f03ac 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb @@ -18,29 +18,50 @@ describe "#execute" do context "when ActiveRecord::Deadlocked occurs" do - it "displays latest detected deadlock" do + def cause_deadlock(role:) ths = [] ths << Thread.new do - User.transaction do - User.lock.find_by!(name: 'foo') - sleep 0.1 - User.lock.find_by!(name: 'bar') + ActiveRecord::Base.connected_to(role: role) do + User.transaction do + User.lock.find_by!(name: 'foo') + sleep 0.1 + User.lock.find_by!(name: 'bar') + end end end ths << Thread.new do - User.transaction do - User.lock.find_by!(name: 'bar') - sleep 0.1 - User.lock.find_by!(name: 'foo') + ActiveRecord::Base.connected_to(role: role) do + User.transaction do + User.lock.find_by!(name: 'bar') + sleep 0.1 + User.lock.find_by!(name: 'foo') + end end end - expect { - ths.each(&:join) - }.to raise_error(ActiveRecord::Deadlocked) - expect(log.string).to include("LATEST DETECTED DEADLOCK") - expect(log.string).to include("WE ROLL BACK TRANSACTION") + ths.each(&:join) + end + + context "when the user has the permission to execute 'SHOW ENGINE INNODB STATUS'" do + it "displays latest detected deadlock" do + expect { + cause_deadlock(role: :writing) + }.to raise_error(ActiveRecord::Deadlocked) + expect(log.string).to include("LATEST DETECTED DEADLOCK") + expect(log.string).to include("WE ROLL BACK TRANSACTION") + end + end + + context "when the user doesn't have the permission to execute 'SHOW ENGINE INNODB STATUS'" do + it "displays an error message" do + expect { + ActiveRecord::Base.connected_to(role: :reading) do + cause_deadlock(role: :reading) + end + }.to raise_error(ActiveRecord::Deadlocked) + expect(log.string).to include("Failed to execute") + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0414ced..4ec8eef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,30 +1,6 @@ require "bundler/setup" require "activerecord/debug_errors" -ActiveRecord::Base.establish_connection( - adapter: "mysql2", - host: ENV['MYSQL_HOST'], - port: ENV['MYSQL_PORT'], - username: ENV['MYSQL_USERNAME'], - password: ENV['MYSQL_PASSWORD'], - database: ENV['MYSQL_DATABASE'], - variables: { - innodb_lock_wait_timeout: 1, - }, -) - -unless ActiveRecord::Base.connection.table_exists?('users') - ActiveRecord::Base.connection.create_table('users') do |t| - t.string 'name' - t.index ['name'], name: 'ux_name', unique: true - end -end - -class User < ActiveRecord::Base; end - -User.find_or_create_by!(name: 'foo') -User.find_or_create_by!(name: 'bar') - RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -35,4 +11,52 @@ class User < ActiveRecord::Base; end config.expect_with :rspec do |c| c.syntax = :expect end + + config.before(:all) do + base_db_config = { + adapter: "mysql2", + host: ENV['MYSQL_HOST'], + port: ENV['MYSQL_PORT'], + username: ENV['MYSQL_USERNAME'], + password: ENV['MYSQL_PASSWORD'], + database: ENV['MYSQL_DATABASE'], + variables: { + innodb_lock_wait_timeout: 1, + }, + } + + user_for_replica = 'activerecord-debug_errors' + ActiveRecord::Base.legacy_connection_handling = false + ActiveRecord::Base.configurations = { + default_env: { + primary: base_db_config, + primary_replica: base_db_config.merge(username: user_for_replica, replica: true), + } + } + + ActiveRecord::Base.establish_connection(:default_env) + + unless ActiveRecord::Base.connection.table_exists?('users') + ActiveRecord::Base.connection.create_table('users') do |t| + t.string 'name' + t.index ['name'], name: 'ux_name', unique: true + end + end + + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + connects_to database: { writing: :primary, reading: :primary_replica } + end + class User < ApplicationRecord; end + + User.find_or_create_by!(name: 'foo') + User.find_or_create_by!(name: 'bar') + + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE USER IF NOT EXISTS '#{user_for_replica}'@'%' IDENTIFIED BY '#{ENV['MYSQL_PASSWORD']}' + SQL + ActiveRecord::Base.connection.execute(<<~SQL) + GRANT SELECT, LOCK TABLES ON *.* To '#{user_for_replica}'@'%' + SQL + end end From 77b43fb3fd4f8b757c722ed83eead601f94cf7e6 Mon Sep 17 00:00:00 2001 From: abicky Date: Thu, 20 Jan 2022 02:18:00 +0900 Subject: [PATCH 02/30] Bump the version number to 0.1.2 --- lib/activerecord/debug_errors/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activerecord/debug_errors/version.rb b/lib/activerecord/debug_errors/version.rb index c61ec85..8f0af65 100644 --- a/lib/activerecord/debug_errors/version.rb +++ b/lib/activerecord/debug_errors/version.rb @@ -1,5 +1,5 @@ module ActiveRecord module DebugErrors - VERSION = "0.1.1" + VERSION = "0.1.2" end end From 1e528d24cfed2e5f2169e204df4f35704dcac9fb Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 17:19:01 +0900 Subject: [PATCH 03/30] Fix typo: occured -> occurred https://www.oxfordlearnersdictionaries.com/definition/english/occur --- README.md | 2 +- .../debug_errors/ext/connection_adapters/connection_pool.rb | 2 +- .../ext/connection_adapters/connection_pool_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4254634..483dd28 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Note that the user requires the PROCESS priviledge to collect the information. When `ActiveRecord::ConnectionTimeoutError` occurs, you can see the information of connection owners (threads): ``` -ActiveRecord::ConnectionTimeoutError occured: +ActiveRecord::ConnectionTimeoutError occurred: connection owners: Thread # status=sleep priority=0 /path/to/activerecord-debug_errors/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb:32:in `join' diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/connection_pool.rb b/lib/activerecord/debug_errors/ext/connection_adapters/connection_pool.rb index f12e9f4..eb4c738 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/connection_pool.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/connection_pool.rb @@ -15,7 +15,7 @@ def acquire_connection(*args) def dump_threads logger = ActiveRecord::Base.logger - logger.error "ActiveRecord::ConnectionTimeoutError occured:" + logger.error "ActiveRecord::ConnectionTimeoutError occurred:" dump_thread = ->(thread) { logger.error " Thread #{thread} status=#{thread.status} priority=#{thread.priority}" diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb index 7a86bc7..30c8c5c 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb @@ -29,7 +29,7 @@ end.each(&:join) }.to raise_error(ActiveRecord::ConnectionTimeoutError) - expect(log.string).to include("ActiveRecord::ConnectionTimeoutError occured:") + expect(log.string).to include("ActiveRecord::ConnectionTimeoutError occurred:") expect(log.string).to include("connection owners:") expect(log.string).to include("other threads") From 08b82840beba7c1cbadeab08f4d1d99d9ec4e738 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 17:21:52 +0900 Subject: [PATCH 04/30] Run test against Ruby 3.2, 3.3 --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 473508f..30bbdc7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,8 @@ jobs: - '2.7' - '3.0' - '3.1' + - '3.2' + - '3.3' activerecord-version: - '6' - '7' From e508ef676f971b083dc0b333d0289fb0aef46658 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 17:32:51 +0900 Subject: [PATCH 05/30] Use legacy_connection_handling option only when it exists ``` 1) ActiveRecord::DebugErrors::DisplayConnectionOwners#execute when ActiveRecord::Deadlocked occurs when the user has the permission to execute 'SHOW ENGINE INNODB STATUS' displays latest detected deadlock Failure/Error: ActiveRecord::Base.legacy_connection_handling = false NoMethodError: undefined method `legacy_connection_handling=' for class ActiveRecord::Base # ./vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.3/lib/active_record/dynamic_matchers.rb:22:in `method_missing' # ./spec/spec_helper.rb:29:in `block (2 levels) in ' ``` --- spec/spec_helper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4ec8eef..6414d2e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -26,7 +26,11 @@ } user_for_replica = 'activerecord-debug_errors' - ActiveRecord::Base.legacy_connection_handling = false + # For compatibility. Rails deprecated since 6.1 and removed this option since 7.1. + # https://github.com/rails/rails/pull/44827/commits/ad52c0a19714a1b87a7d0c626a8b364cf95414cf + if ActiveRecord::Base.respond_to?(:legacy_connection_handling) + ActiveRecord::Base.legacy_connection_handling = false + end ActiveRecord::Base.configurations = { default_env: { primary: base_db_config, From 5f9a8c927f3051444a2e41b0d67a5b95bffda0fc Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 18:59:23 +0900 Subject: [PATCH 06/30] Override `translate_exception_class` instead of `raw_execute` and `execute` Starting with Rails 7.1, `raw_execute` may not be called when executing a query. This pull request introduces `with_raw_connection`, and this method is now called when executing a query. https://github.com/rails/rails/pull/44576/files#diff-460f4e7973c5dd945c51d24df5b0173961190d3645f4e2585fd3003fa1fc0ff7R865 Overriding this method and calling super will result in a LocalJumpError, which is not safe. In this commit, I tried to call the log by hooking into `translate_exception_class`. This method has been around since 4.2, and there should be no changes to the interface since 6.0. https://github.com/rails/rails/commit/5e5118aa8ba821af6e615cfd2903b848ff8a9177#diff-460f4e7973c5dd945c51d24df5b0173961190d3645f4e2585fd3003fa1fc0ff7R356 --- .../abstract_mysql_adapter.rb | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb index 77f3f25..36caa82 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb @@ -4,32 +4,25 @@ module ActiveRecord module DebugErrors module DisplayMySQLInformation - # Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute - # cf. https://github.com/rails/rails/pull/43097 - if ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.private_method_defined?(:raw_execute) - method_name = :raw_execute - else - method_name = :execute - end - define_method(method_name) do |*args, **kwargs| - super(*args, **kwargs) - rescue ActiveRecord::Deadlocked - if logger - logger.error "ActiveRecord::Deadlocked occurred:" - display_latest_detected_deadlock - end - raise - rescue ActiveRecord::LockWaitTimeout - if logger - logger.error "ActiveRecord::LockWaitTimeout occurred:" - display_transactions - display_processlist + private + + # Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` + # so that it obtains an error happened on query executions. + def translate_exception_class(*args, **kwargs) + if args[0].is_a?(ActiveRecord::Deadlocked) + if logger + logger.error "ActiveRecord::Deadlocked occurred:" + display_latest_detected_deadlock + end + elsif args[0].is_a?(ActiveRecord::LockWaitTimeout) + if logger + logger.error "ActiveRecord::LockWaitTimeout occurred:" + display_transactions + display_processlist + end end - raise + super(*args, **kwargs) end - private method_name if method_name == :raw_execute - - private def display_latest_detected_deadlock display_innodb_status_section("LATEST DETECTED DEADLOCK") From 1688bd02da7c7d3cfa05d063dea9e947b953e12a Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 20:06:07 +0900 Subject: [PATCH 07/30] Cause `ActiveRecord::ConnectionTimeoutError` even in Rails 7.1.3 `acquire_connection` in connection_pool is now smarter in Rails 7.1.3. Even when running existing tests in Rails7.1.3, `ActiveRecord::ConnectionTimeoutError` no longer occurs because a connection can be obtained by `try_to_checkout_new_connection` after reap. https://github.com/rails/rails/compare/v7.1.2..v7.1.3#diff-642b90553b888bd2c724c093a1a685a5408a7d8293f3751366c25dc548936eb7R660 --- .../ext/connection_adapters/connection_pool_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb index 7a86bc7..e5bca73 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb @@ -18,13 +18,16 @@ describe "#acquire_connection" do it "displays connection owners and other threads" do + ActiveRecord::Base.connection_pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout + Thread.new { sleep 10 } # another thread expect { ActiveRecord::Base.connection # Ensure to acquire a connection Array.new(ActiveRecord::Base.connection_pool.size) do Thread.new do - ActiveRecord::Base.connection_pool.checkout(0.1) + ActiveRecord::Base.connection_pool.checkout + sleep 0.001 end end.each(&:join) }.to raise_error(ActiveRecord::ConnectionTimeoutError) From 6214941f408240ee2279d2126dec8ca0a152112e Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 20:21:24 +0900 Subject: [PATCH 08/30] Retrieve `execute` hook for 6.0 and 6.1 --- .../abstract_mysql_adapter.rb | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb index 36caa82..052db5c 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb @@ -4,26 +4,48 @@ module ActiveRecord module DebugErrors module DisplayMySQLInformation + # For Rails 6.0 or 6.1. Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute + # cf. https://github.com/rails/rails/pull/43097 + if ActiveRecord.version < Gem::Version.new("7.0.0") + def execute(*args, **kwargs) + super(*args, **kwargs) + rescue ActiveRecord::Deadlocked + handle_deadlocked + raise + rescue ActiveRecord::LockWaitTimeout + handle_lock_wait_timeout + raise + end + end + private - # Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` + # For Rails 7.0 or later. Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` # so that it obtains an error happened on query executions. def translate_exception_class(*args, **kwargs) if args[0].is_a?(ActiveRecord::Deadlocked) - if logger - logger.error "ActiveRecord::Deadlocked occurred:" - display_latest_detected_deadlock - end + handle_deadlocked elsif args[0].is_a?(ActiveRecord::LockWaitTimeout) - if logger - logger.error "ActiveRecord::LockWaitTimeout occurred:" - display_transactions - display_processlist - end + handle_lock_wait_timeout end super(*args, **kwargs) end + def handle_deadlocked + if logger + logger.error "ActiveRecord::Deadlocked occurred:" + display_latest_detected_deadlock + end + end + + def handle_lock_wait_timeout + if logger + logger.error "ActiveRecord::LockWaitTimeout occurred:" + display_transactions + display_processlist + end + end + def display_latest_detected_deadlock display_innodb_status_section("LATEST DETECTED DEADLOCK") end From ca6d8e4446a48dfbf40e63e926b89055c348de28 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 20:29:05 +0900 Subject: [PATCH 09/30] Add test matrix: Rails minor versions should be tested --- .github/workflows/test.yml | 14 +++++++++----- gemfiles/activerecord_6.gemfile | 2 -- gemfiles/activerecord_6_0.gemfile | 2 ++ gemfiles/activerecord_6_1.gemfile | 2 ++ gemfiles/activerecord_7.gemfile | 2 -- gemfiles/activerecord_7_0.gemfile | 2 ++ gemfiles/activerecord_7_1.gemfile | 2 ++ 7 files changed, 17 insertions(+), 9 deletions(-) delete mode 100644 gemfiles/activerecord_6.gemfile create mode 100644 gemfiles/activerecord_6_0.gemfile create mode 100644 gemfiles/activerecord_6_1.gemfile delete mode 100644 gemfiles/activerecord_7.gemfile create mode 100644 gemfiles/activerecord_7_0.gemfile create mode 100644 gemfiles/activerecord_7_1.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30bbdc7..273fe6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,12 +20,16 @@ jobs: - '3.2' - '3.3' activerecord-version: - - '6' - - '7' + - '6_0' + - '6_1' + - '7_0' + - '7_1' exclude: - - # activerecord-7 doesn't support Ruby 2.6 - ruby-version: '2.6' - activerecord-version: '7' + # activerecord-7 doesn't support Ruby 2.6 + - ruby-version: '2.6' + activerecord-version: '7_0' + - ruby-version: '2.6' + activerecord-version: '7_1' services: mysql: diff --git a/gemfiles/activerecord_6.gemfile b/gemfiles/activerecord_6.gemfile deleted file mode 100644 index dcd6b25..0000000 --- a/gemfiles/activerecord_6.gemfile +++ /dev/null @@ -1,2 +0,0 @@ -eval_gemfile("../Gemfile") -gem "activerecord", "~> 6.0" diff --git a/gemfiles/activerecord_6_0.gemfile b/gemfiles/activerecord_6_0.gemfile new file mode 100644 index 0000000..ee55b54 --- /dev/null +++ b/gemfiles/activerecord_6_0.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 6.0.0" diff --git a/gemfiles/activerecord_6_1.gemfile b/gemfiles/activerecord_6_1.gemfile new file mode 100644 index 0000000..44404eb --- /dev/null +++ b/gemfiles/activerecord_6_1.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 6.1.0" diff --git a/gemfiles/activerecord_7.gemfile b/gemfiles/activerecord_7.gemfile deleted file mode 100644 index 830ef3f..0000000 --- a/gemfiles/activerecord_7.gemfile +++ /dev/null @@ -1,2 +0,0 @@ -eval_gemfile("../Gemfile") -gem "activerecord", "~> 7.0" diff --git a/gemfiles/activerecord_7_0.gemfile b/gemfiles/activerecord_7_0.gemfile new file mode 100644 index 0000000..4a24b63 --- /dev/null +++ b/gemfiles/activerecord_7_0.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 7.0.0" diff --git a/gemfiles/activerecord_7_1.gemfile b/gemfiles/activerecord_7_1.gemfile new file mode 100644 index 0000000..afdf59b --- /dev/null +++ b/gemfiles/activerecord_7_1.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 7.1.0" From 9cc944f97e2ed714634d87331782e317ecba4162 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 20:35:00 +0900 Subject: [PATCH 10/30] Retrieve `raw_execute` hook for 7.0 --- .../abstract_mysql_adapter.rb | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb index 052db5c..730cc12 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb @@ -4,10 +4,16 @@ module ActiveRecord module DebugErrors module DisplayMySQLInformation - # For Rails 6.0 or 6.1. Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute - # cf. https://github.com/rails/rails/pull/43097 - if ActiveRecord.version < Gem::Version.new("7.0.0") - def execute(*args, **kwargs) + # For Rails 6.0, 6.1, 7.0. + if ActiveRecord.version < Gem::Version.new("7.1.0") + # Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute + # cf. https://github.com/rails/rails/pull/43097 + if ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.private_method_defined?(:raw_execute) + method_name = :raw_execute + else + method_name = :execute + end + define_method(method_name) do |*args, **kwargs| super(*args, **kwargs) rescue ActiveRecord::Deadlocked handle_deadlocked @@ -16,21 +22,22 @@ def execute(*args, **kwargs) handle_lock_wait_timeout raise end + private method_name if method_name == :raw_execute + elsif ActiveRecord.version >= Gem::Version.new("7.1.0") + # For Rails 7.1 or later. Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` + # so that it obtains an error happened on query executions. + private def translate_exception_class(*args, **kwargs) + if args[0].is_a?(ActiveRecord::Deadlocked) + handle_deadlocked + elsif args[0].is_a?(ActiveRecord::LockWaitTimeout) + handle_lock_wait_timeout + end + super(*args, **kwargs) + end end private - # For Rails 7.0 or later. Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` - # so that it obtains an error happened on query executions. - def translate_exception_class(*args, **kwargs) - if args[0].is_a?(ActiveRecord::Deadlocked) - handle_deadlocked - elsif args[0].is_a?(ActiveRecord::LockWaitTimeout) - handle_lock_wait_timeout - end - super(*args, **kwargs) - end - def handle_deadlocked if logger logger.error "ActiveRecord::Deadlocked occurred:" From ee782042397e9551bcebe55883118e992c91c398 Mon Sep 17 00:00:00 2001 From: Masato Ohba Date: Sun, 26 May 2024 20:46:26 +0900 Subject: [PATCH 11/30] Use actions/checkout@v4 since v2 is already outdated. https://github.com/actions/checkout --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 473508f..c974df1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.activerecord-version }}.gemfile steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@v1 with: From d2a4654265a72dc34b10b7f81c36876bfdf74f45 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 26 May 2024 23:59:28 +0900 Subject: [PATCH 12/30] Use more stable way to cause `ConnectionTimeoutError` https://github.com/abicky/activerecord-debug_errors/pull/3/files#r1615209397 --- .../connection_adapters/connection_pool_spec.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb index e5bca73..97e3803 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb @@ -18,16 +18,22 @@ describe "#acquire_connection" do it "displays connection owners and other threads" do - ActiveRecord::Base.connection_pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout - Thread.new { sleep 10 } # another thread + mutex = Mutex.new + cv = ConditionVariable.new + expect { ActiveRecord::Base.connection # Ensure to acquire a connection Array.new(ActiveRecord::Base.connection_pool.size) do Thread.new do - ActiveRecord::Base.connection_pool.checkout - sleep 0.001 + mutex.synchronize do + ActiveRecord::Base.connection_pool.checkout(0.1) + cv.wait(mutex, 1) + rescue + cv.broadcast + raise + end end end.each(&:join) }.to raise_error(ActiveRecord::ConnectionTimeoutError) From 6dbd03f2b11d973c9429913cb770ed738cc26f42 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Mon, 27 May 2024 00:08:00 +0900 Subject: [PATCH 13/30] Retrieve the original prepend way so that it works even in 7.1 https://github.com/abicky/activerecord-debug_errors/pull/3/files#r1615204591 --- .../abstract_mysql_adapter.rb | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb index 730cc12..c1f5bfc 100644 --- a/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb +++ b/lib/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter.rb @@ -4,54 +4,32 @@ module ActiveRecord module DebugErrors module DisplayMySQLInformation - # For Rails 6.0, 6.1, 7.0. - if ActiveRecord.version < Gem::Version.new("7.1.0") - # Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute - # cf. https://github.com/rails/rails/pull/43097 - if ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.private_method_defined?(:raw_execute) - method_name = :raw_execute - else - method_name = :execute - end - define_method(method_name) do |*args, **kwargs| - super(*args, **kwargs) - rescue ActiveRecord::Deadlocked - handle_deadlocked - raise - rescue ActiveRecord::LockWaitTimeout - handle_lock_wait_timeout - raise - end - private method_name if method_name == :raw_execute - elsif ActiveRecord.version >= Gem::Version.new("7.1.0") - # For Rails 7.1 or later. Override `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` - # so that it obtains an error happened on query executions. - private def translate_exception_class(*args, **kwargs) - if args[0].is_a?(ActiveRecord::Deadlocked) - handle_deadlocked - elsif args[0].is_a?(ActiveRecord::LockWaitTimeout) - handle_lock_wait_timeout - end - super(*args, **kwargs) - end + # Rails 7 or later never calls ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#execute + # cf. https://github.com/rails/rails/pull/43097 + if ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.private_method_defined?(:raw_execute) + method_name = :raw_execute + else + method_name = :execute end - - private - - def handle_deadlocked + define_method(method_name) do |*args, **kwargs| + super(*args, **kwargs) + rescue ActiveRecord::Deadlocked if logger logger.error "ActiveRecord::Deadlocked occurred:" display_latest_detected_deadlock end - end - - def handle_lock_wait_timeout + raise + rescue ActiveRecord::LockWaitTimeout if logger logger.error "ActiveRecord::LockWaitTimeout occurred:" display_transactions display_processlist end + raise end + private method_name if method_name == :raw_execute + + private def display_latest_detected_deadlock display_innodb_status_section("LATEST DETECTED DEADLOCK") @@ -102,6 +80,13 @@ def display_innodb_status_section(section_name) end end +ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.descendants.each do |adapter| + adapter.prepend(ActiveRecord::DebugErrors::DisplayMySQLInformation) +end + class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter - prepend ActiveRecord::DebugErrors::DisplayMySQLInformation + def self.inherited(base) + super + base.prepend(ActiveRecord::DebugErrors::DisplayMySQLInformation) + end end From 613dbdf9d051e71a2bc80ee7e7da1736ddcc54d5 Mon Sep 17 00:00:00 2001 From: abicky Date: Mon, 27 May 2024 00:53:59 +0900 Subject: [PATCH 14/30] Bump the version number to 0.1.3 --- lib/activerecord/debug_errors/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activerecord/debug_errors/version.rb b/lib/activerecord/debug_errors/version.rb index 8f0af65..c39176a 100644 --- a/lib/activerecord/debug_errors/version.rb +++ b/lib/activerecord/debug_errors/version.rb @@ -1,5 +1,5 @@ module ActiveRecord module DebugErrors - VERSION = "0.1.2" + VERSION = "0.1.3" end end From 466d7d6388f0d85e17ac868b8a5fd8b2a7c24400 Mon Sep 17 00:00:00 2001 From: Masato Ohba Date: Mon, 27 May 2024 21:38:31 +0900 Subject: [PATCH 15/30] Fix broken badge on README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 483dd28..69aa4ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ActiveRecord::DebugErrors -![](https://github.com/abicky/activerecord-debug_errors/workflows/CI/badge.svg?branch=master) +[![CI](https://github.com/abicky/activerecord-debug_errors/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/abicky/activerecord-debug_errors/actions/workflows/test.yml) ActiveRecord::DebugErrors is an extension of activerecord to display useful debug logs on errors. From 1e4e120153d625bc4b5c6a9c65fa177e437361f9 Mon Sep 17 00:00:00 2001 From: abicky Date: Tue, 28 May 2024 08:34:16 +0900 Subject: [PATCH 16/30] Delete unnecessary commands --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f627486..9324975 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,10 +55,8 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Build and test with Rake + - name: Run tests run: | - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 bundle exec rake env: MYSQL_HOST: 127.0.0.1 From a0e840fd6e1d39a6c5ea8c64dda97242f7fc0317 Mon Sep 17 00:00:00 2001 From: abicky Date: Tue, 28 May 2024 08:41:37 +0900 Subject: [PATCH 17/30] Run tests on a regular basis --- .github/workflows/test.yml | 4 ++++ activerecord-debug_errors.gemspec | 2 +- gemfiles/activerecord_latest.gemfile | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 gemfiles/activerecord_latest.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9324975..cfeedd0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,8 @@ name: CI on: push: pull_request: + schedule: + - cron: '0 13 * * *' jobs: build: @@ -19,11 +21,13 @@ jobs: - '3.1' - '3.2' - '3.3' + - '3' activerecord-version: - '6_0' - '6_1' - '7_0' - '7_1' + - 'latest' exclude: # activerecord-7 doesn't support Ruby 2.6 - ruby-version: '2.6' diff --git a/activerecord-debug_errors.gemspec b/activerecord-debug_errors.gemspec index fceb061..d7de851 100644 --- a/activerecord-debug_errors.gemspec +++ b/activerecord-debug_errors.gemspec @@ -22,5 +22,5 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_runtime_dependency "activerecord", ">= 6", "< 8" + spec.add_runtime_dependency "activerecord", ">= 6" end diff --git a/gemfiles/activerecord_latest.gemfile b/gemfiles/activerecord_latest.gemfile new file mode 100644 index 0000000..dd22286 --- /dev/null +++ b/gemfiles/activerecord_latest.gemfile @@ -0,0 +1 @@ +eval_gemfile("../Gemfile") From be950f4032cb78d7f3302513817161664fff447e Mon Sep 17 00:00:00 2001 From: abicky Date: Tue, 28 May 2024 08:53:25 +0900 Subject: [PATCH 18/30] Run tests with MySQL 8.0 as well --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cfeedd0..f8869b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,9 @@ jobs: - '7_0' - '7_1' - 'latest' + mysql-version: + - '5.7' + - '8.0' exclude: # activerecord-7 doesn't support Ruby 2.6 - ruby-version: '2.6' @@ -37,7 +40,7 @@ jobs: services: mysql: - image: mysql:5.7 + image: mysql:${{ matrix.mysql-version }} ports: - 3306:3306 options: >- From 3cc3606cd699f062b4ab7ea291068e6bec3193b7 Mon Sep 17 00:00:00 2001 From: abicky Date: Tue, 28 May 2024 09:03:45 +0900 Subject: [PATCH 19/30] Stop supporting EOL versions --- .github/workflows/test.yml | 9 --------- activerecord-debug_errors.gemspec | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8869b5..512446b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,15 +15,12 @@ jobs: fail-fast: false matrix: ruby-version: - - '2.6' - - '2.7' - '3.0' - '3.1' - '3.2' - '3.3' - '3' activerecord-version: - - '6_0' - '6_1' - '7_0' - '7_1' @@ -31,12 +28,6 @@ jobs: mysql-version: - '5.7' - '8.0' - exclude: - # activerecord-7 doesn't support Ruby 2.6 - - ruby-version: '2.6' - activerecord-version: '7_0' - - ruby-version: '2.6' - activerecord-version: '7_1' services: mysql: diff --git a/activerecord-debug_errors.gemspec b/activerecord-debug_errors.gemspec index d7de851..a0c52bb 100644 --- a/activerecord-debug_errors.gemspec +++ b/activerecord-debug_errors.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.description = %q{ActiveRecord::DebugErrors is an extension of activerecord to display useful debug logs on errors.} spec.homepage = "https://github.com/abicky/activerecord-debug_errors" spec.license = "MIT" - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") + spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage @@ -22,5 +22,5 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_runtime_dependency "activerecord", ">= 6" + spec.add_runtime_dependency "activerecord", ">= 6.1" end From aefa90edee25bacda5843c7ddfa7808282cbe0c0 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Wed, 21 Aug 2024 23:59:46 +0900 Subject: [PATCH 20/30] Run CI tests against Rails 7.2 --- .github/workflows/test.yml | 5 +++++ gemfiles/activerecord_7_2.gemfile | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 gemfiles/activerecord_7_2.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 512446b..8ae14ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,10 +24,15 @@ jobs: - '6_1' - '7_0' - '7_1' + - '7_2' - 'latest' mysql-version: - '5.7' - '8.0' + exclude: + # activerecord-7 doesn't support Ruby 2.6 + - ruby-version: '3.0' + activerecord-version: '7_2' services: mysql: diff --git a/gemfiles/activerecord_7_2.gemfile b/gemfiles/activerecord_7_2.gemfile new file mode 100644 index 0000000..74aab1c --- /dev/null +++ b/gemfiles/activerecord_7_2.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 7.2.0" From 8b9a73d8154f9127960bab4268893e7474e43cc5 Mon Sep 17 00:00:00 2001 From: Masato Ohba Date: Wed, 28 Aug 2024 09:21:16 +0900 Subject: [PATCH 21/30] Update .github/workflows/test.yml Co-authored-by: Takeshi Arabiki --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ae14ca..63525e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - '5.7' - '8.0' exclude: - # activerecord-7 doesn't support Ruby 2.6 + # activerecord-7.2 requires Ruby 3.1.0 or later - ruby-version: '3.0' activerecord-version: '7_2' From 0d57afd69883ba35e7f2d89eee365f7c40b8be3e Mon Sep 17 00:00:00 2001 From: abicky Date: Tue, 1 Oct 2024 00:05:19 +0900 Subject: [PATCH 22/30] Fix flaky test This commit fixes the flaky test "ActiveRecord::DebugErrors::DisplayConnectionOwners#execute when ActiveRecord::LockWaitTimeout occurs displays transactions and processlist." I suspect there is more than 1 second time delay between the start time of the two threads, and CyclicBarrier shortens the delay. --- .../abstract_mysql_adapter_spec.rb | 26 +++++++++---------- .../connection_pool_spec.rb | 17 ++++++------ spec/support/cyclic_barrier.rb | 21 +++++++++++++++ 3 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 spec/support/cyclic_barrier.rb diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb index 44f03ac..d76df70 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" +require "support/cyclic_barrier" RSpec.describe ActiveRecord::DebugErrors::DisplayConnectionOwners do let(:log) { StringIO.new } @@ -19,12 +20,14 @@ describe "#execute" do context "when ActiveRecord::Deadlocked occurs" do def cause_deadlock(role:) + barrier = CyclicBarrier.new(2) + ths = [] ths << Thread.new do ActiveRecord::Base.connected_to(role: role) do User.transaction do User.lock.find_by!(name: 'foo') - sleep 0.1 + barrier.await(1) User.lock.find_by!(name: 'bar') end end @@ -34,7 +37,7 @@ def cause_deadlock(role:) ActiveRecord::Base.connected_to(role: role) do User.transaction do User.lock.find_by!(name: 'bar') - sleep 0.1 + barrier.await(1) User.lock.find_by!(name: 'foo') end end @@ -67,18 +70,15 @@ def cause_deadlock(role:) context "when ActiveRecord::LockWaitTimeout occurs" do it "displays transactions and processlist" do - ths = [] - ths << Thread.new do - User.transaction do - User.lock.find_by!(name: 'foo') - sleep 2 - end - end + barrier = CyclicBarrier.new(2) - ths << Thread.new do - User.transaction do - User.lock.find_by!(name: 'foo') - sleep 2 + ths = Array.new(2) do + Thread.new do + User.transaction do + barrier.await(1) + User.lock.find_by!(name: 'foo') + sleep 2 + end end end diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb index 8f5b932..556bf4d 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/connection_pool_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" +require "support/cyclic_barrier" RSpec.describe ActiveRecord::DebugErrors::DisplayConnectionOwners do let(:log) { StringIO.new } @@ -20,20 +21,18 @@ it "displays connection owners and other threads" do Thread.new { sleep 10 } # another thread - mutex = Mutex.new - cv = ConditionVariable.new + barrier = CyclicBarrier.new(ActiveRecord::Base.connection_pool.size) expect { ActiveRecord::Base.connection # Ensure to acquire a connection Array.new(ActiveRecord::Base.connection_pool.size) do Thread.new do - mutex.synchronize do - ActiveRecord::Base.connection_pool.checkout(0.1) - cv.wait(mutex, 1) - rescue - cv.broadcast - raise - end + ActiveRecord::Base.connection_pool.checkout(0.1) + barrier.await(1) + rescue Timeout::Error + # CyclicBarrier#await is expected to raise Timeout::Error + # because it is not called ActiveRecord::Base.connection_pool.size times + # due to ActiveRecord::ConnectionTimeoutError end end.each(&:join) }.to raise_error(ActiveRecord::ConnectionTimeoutError) diff --git a/spec/support/cyclic_barrier.rb b/spec/support/cyclic_barrier.rb new file mode 100644 index 0000000..a588f83 --- /dev/null +++ b/spec/support/cyclic_barrier.rb @@ -0,0 +1,21 @@ +# This class is a simple implementation of CyclicBarrier in Java +class CyclicBarrier + def initialize(parties) + @cv = ConditionVariable.new + @mutex = Mutex.new + @parties = parties + @number_waiting = 0 + end + + def await(timeout = nil) + @mutex.synchronize do + @number_waiting += 1 + if @number_waiting == @parties + @cv.broadcast + else + @cv.wait(@mutex, timeout) + raise Timeout::Error if @number_waiting != @parties + end + end + end +end From d835edd75df211f0654a3a85215aa495ef3961e3 Mon Sep 17 00:00:00 2001 From: abicky Date: Sat, 5 Oct 2024 20:47:10 +0900 Subject: [PATCH 23/30] Increase sleep time to fix flaky test InnoDB seems to raise the error "Lock wait timeout exceeded" more than innodb_lock_wait_timeout seconds. --- .../ext/connection_adapters/abstract_mysql_adapter_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb index d76df70..03d0dfd 100644 --- a/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb +++ b/spec/activerecord/debug_errors/ext/connection_adapters/abstract_mysql_adapter_spec.rb @@ -74,10 +74,11 @@ def cause_deadlock(role:) ths = Array.new(2) do Thread.new do + Thread.current.abort_on_exception = true User.transaction do barrier.await(1) User.lock.find_by!(name: 'foo') - sleep 2 + sleep 5 end end end From 95f83ab1b898ac06c22b18bf88680516c253660e Mon Sep 17 00:00:00 2001 From: abicky Date: Fri, 8 Nov 2024 08:04:03 +0900 Subject: [PATCH 24/30] Support activerecord 8.0 explicitly --- .github/workflows/test.yml | 14 +++++++++++++- gemfiles/activerecord_8_0.gemfile | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 gemfiles/activerecord_8_0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63525e4..930f4a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,15 +25,25 @@ jobs: - '7_0' - '7_1' - '7_2' + - '8_0' - 'latest' mysql-version: - '5.7' - '8.0' exclude: - # activerecord-7.2 requires Ruby 3.1.0 or later + # Exclude conditions that don't meat the minimal requirement - ruby-version: '3.0' activerecord-version: '7_2' + - ruby-version: '3.0' + activerecord-version: '8_0' + - ruby-version: '3.1' + activerecord-version: '8_0' + # Exclude duplicate conditions + - ruby-version: '3.0' + activerecord-version: 'latest' # equivalent to '7_1' + - ruby-version: '3.1' + activerecord-version: 'latest' # equivalent to '7_2' services: mysql: image: mysql:${{ matrix.mysql-version }} @@ -58,6 +68,8 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true + - name: Show activerecord version + run: bundle info activerecord - name: Run tests run: | bundle exec rake diff --git a/gemfiles/activerecord_8_0.gemfile b/gemfiles/activerecord_8_0.gemfile new file mode 100644 index 0000000..b0bbd51 --- /dev/null +++ b/gemfiles/activerecord_8_0.gemfile @@ -0,0 +1,2 @@ +eval_gemfile("../Gemfile") +gem "activerecord", "~> 8.0.0" From d97cd229f18491a6c923896112fab57411552585 Mon Sep 17 00:00:00 2001 From: abicky Date: Fri, 8 Nov 2024 09:03:13 +0900 Subject: [PATCH 25/30] Drop support of Ruby 3.0 and MySQL 5.7 Because they have already reached their EOL. --- .github/workflows/test.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 930f4a6..521c9c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,6 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.0' - '3.1' - '3.2' - '3.3' @@ -28,20 +27,13 @@ jobs: - '8_0' - 'latest' mysql-version: - - '5.7' - '8.0' exclude: # Exclude conditions that don't meat the minimal requirement - - ruby-version: '3.0' - activerecord-version: '7_2' - - ruby-version: '3.0' - activerecord-version: '8_0' - ruby-version: '3.1' activerecord-version: '8_0' # Exclude duplicate conditions - - ruby-version: '3.0' - activerecord-version: 'latest' # equivalent to '7_1' - ruby-version: '3.1' activerecord-version: 'latest' # equivalent to '7_2' services: From 9de9e5de613468136727960509ae2c201aada051 Mon Sep 17 00:00:00 2001 From: abicky Date: Mon, 23 Dec 2024 12:43:17 +0900 Subject: [PATCH 26/30] Bump the version number to 0.1.4 --- lib/activerecord/debug_errors/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activerecord/debug_errors/version.rb b/lib/activerecord/debug_errors/version.rb index c39176a..9e96801 100644 --- a/lib/activerecord/debug_errors/version.rb +++ b/lib/activerecord/debug_errors/version.rb @@ -1,5 +1,5 @@ module ActiveRecord module DebugErrors - VERSION = "0.1.3" + VERSION = "0.1.4" end end From 43e55ac03b7c8836556588b6946b0f9e6d52f827 Mon Sep 17 00:00:00 2001 From: abicky Date: Sat, 28 Dec 2024 00:41:54 +0900 Subject: [PATCH 27/30] Add bundled gems These gems have become bundled gems from default gems since Ruby 3.4.0. cf. https://docs.ruby-lang.org/en/3.4/NEWS_md.html#label-Stdlib+updates --- gemfiles/activerecord_6_1.gemfile | 3 +++ gemfiles/activerecord_7_0.gemfile | 2 ++ 2 files changed, 5 insertions(+) diff --git a/gemfiles/activerecord_6_1.gemfile b/gemfiles/activerecord_6_1.gemfile index 44404eb..45a7048 100644 --- a/gemfiles/activerecord_6_1.gemfile +++ b/gemfiles/activerecord_6_1.gemfile @@ -1,2 +1,5 @@ eval_gemfile("../Gemfile") gem "activerecord", "~> 6.1.0" +gem "base64" +gem "bigdecimal" +gem "mutex_m" diff --git a/gemfiles/activerecord_7_0.gemfile b/gemfiles/activerecord_7_0.gemfile index 4a24b63..2d07c82 100644 --- a/gemfiles/activerecord_7_0.gemfile +++ b/gemfiles/activerecord_7_0.gemfile @@ -1,2 +1,4 @@ eval_gemfile("../Gemfile") gem "activerecord", "~> 7.0.0" +gem "mutex_m" +gem "bigdecimal" From aa4f2d5b2b41c5d66f696bce44e834db221f3ae5 Mon Sep 17 00:00:00 2001 From: abicky Date: Sat, 28 Dec 2024 00:47:58 +0900 Subject: [PATCH 28/30] Delete unnecessary code and file --- gemfiles/activerecord_6_0.gemfile | 2 -- spec/spec_helper.rb | 5 ----- 2 files changed, 7 deletions(-) delete mode 100644 gemfiles/activerecord_6_0.gemfile diff --git a/gemfiles/activerecord_6_0.gemfile b/gemfiles/activerecord_6_0.gemfile deleted file mode 100644 index ee55b54..0000000 --- a/gemfiles/activerecord_6_0.gemfile +++ /dev/null @@ -1,2 +0,0 @@ -eval_gemfile("../Gemfile") -gem "activerecord", "~> 6.0.0" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6414d2e..d6d7b42 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -26,11 +26,6 @@ } user_for_replica = 'activerecord-debug_errors' - # For compatibility. Rails deprecated since 6.1 and removed this option since 7.1. - # https://github.com/rails/rails/pull/44827/commits/ad52c0a19714a1b87a7d0c626a8b364cf95414cf - if ActiveRecord::Base.respond_to?(:legacy_connection_handling) - ActiveRecord::Base.legacy_connection_handling = false - end ActiveRecord::Base.configurations = { default_env: { primary: base_db_config, From 33d1264df979ed774dee3f8ff7d73abb27287dee Mon Sep 17 00:00:00 2001 From: abicky Date: Sat, 28 Dec 2024 00:51:09 +0900 Subject: [PATCH 29/30] Add Ruby 3.4 test case --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 521c9c9..b862225 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ jobs: - '3.1' - '3.2' - '3.3' + - '3.4' - '3' activerecord-version: - '6_1' From 951a7287e2023e95e641b48de38d7a716ac5436f Mon Sep 17 00:00:00 2001 From: abicky Date: Sun, 19 Jan 2025 00:53:39 +0900 Subject: [PATCH 30/30] Fix "uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger" --- spec/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d6d7b42..534d250 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,8 @@ require "bundler/setup" +if Bundler.definition.specs.find { |s| s.name == "activerecord" }.version < Gem::Version.new("7.1") + # Workaround for https://github.com/rails/rails/issues/54260 + require "logger" +end require "activerecord/debug_errors" RSpec.configure do |config|