From 75c62711f8b22dcefbd2ecc38c4635977caaddaf Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Mon, 5 Jan 2026 21:11:12 +0100 Subject: [PATCH 1/4] Adapt tests to Rails 8.2+ re-introduction of `enqueue_after_transaction_commit` See https://github.com/rails/rails/pull/55788 This setting has gone through different stages, and we must account for them: - Rails 7.1: Method not available (no deferred enqueue) - Rails 7.2: :default (defers to adapter's method, which returns true in Solid Queue's case) - Rails 8.0-8.1: false (no deferred enqueue, the setting was deprecated) - Rails 8.2: true (deferred enqueue enabled by default) --- test/integration/concurrency_controls_test.rb | 5 +++-- test/models/solid_queue/job_test.rb | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/integration/concurrency_controls_test.rb b/test/integration/concurrency_controls_test.rb index b3be95c5..d9e72b85 100644 --- a/test/integration/concurrency_controls_test.rb +++ b/test/integration/concurrency_controls_test.rb @@ -178,8 +178,9 @@ class ConcurrencyControlsTest < ActiveSupport::TestCase end test "verify transactions remain valid after Job creation conflicts via limits_concurrency" do - # Doesn't work with enqueue_after_transaction_commit? true on SolidQueueAdapter, but only Rails 7.2 uses this - skip if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 2 + # Doesn't work when enqueue_after_transaction_commit is enabled + skip if ActiveJob::Base.respond_to?(:enqueue_after_transaction_commit) && + [ true, :default ].include?(ActiveJob::Base.enqueue_after_transaction_commit) ActiveRecord::Base.transaction do NonOverlappingUpdateResultJob.perform_later(@result, name: "A", pause: 0.2.seconds) diff --git a/test/models/solid_queue/job_test.rb b/test/models/solid_queue/job_test.rb index 201e6d72..47702bd1 100644 --- a/test/models/solid_queue/job_test.rb +++ b/test/models/solid_queue/job_test.rb @@ -340,8 +340,9 @@ class DiscardableNonOverlappingGroupedJob2 < NonOverlappingJob end test "enqueue successfully inside a rolled-back transaction in the app DB" do - # Doesn't work with enqueue_after_transaction_commit? true on SolidQueueAdapter, but only Rails 7.2 uses this - skip if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 2 + # Doesn't work when enqueue_after_transaction_commit is enabled + skip if ActiveJob::Base.respond_to?(:enqueue_after_transaction_commit) && + [ true, :default ].include?(ActiveJob::Base.enqueue_after_transaction_commit) assert_difference -> { SolidQueue::Job.count } do assert_no_difference -> { JobResult.count } do JobResult.transaction do From dfcf647c7353bf7cf025d48225e6de595d38e895 Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Mon, 5 Jan 2026 21:27:47 +0100 Subject: [PATCH 2/4] Skip Ruby 3.2 with `rails_main` Rails main now requires Ruby >= 3.3 --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc6c9a5e..07b3475f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,6 +40,8 @@ jobs: gemfile: rails_8_1 - ruby-version: "3.1" gemfile: rails_main + - ruby-version: "3.2" + gemfile: rails_main services: mysql: image: mysql:8.0.31 From ff742bfc1d734104ac3adb0b6ae5759f62c73484 Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Tue, 6 Jan 2026 10:45:26 +0100 Subject: [PATCH 3/4] Don't force SQLite immediate transaction for Rails >= 8 These are default built-in since Rails 8 See: https://github.com/rails/rails/pull/50371 --- test/dummy/config/initializers/sqlite3.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/dummy/config/initializers/sqlite3.rb b/test/dummy/config/initializers/sqlite3.rb index 4a29f657..a5e45c33 100644 --- a/test/dummy/config/initializers/sqlite3.rb +++ b/test/dummy/config/initializers/sqlite3.rb @@ -1,10 +1,8 @@ module SqliteImmediateTransactions def begin_db_transaction - if Rails.gem_version < Gem::Version.new("8.2") - log("begin immediate transaction", "TRANSACTION") do - with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn| - conn.transaction(:immediate) - end + log("begin immediate transaction", "TRANSACTION") do + with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn| + conn.transaction(:immediate) end end end @@ -26,7 +24,10 @@ def configure_connection ActiveSupport.on_load :active_record do if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) - ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SqliteImmediateTransactions + # Rails 8.0+ has immediate transactions built-in + if Rails::VERSION::MAJOR < 8 + ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SqliteImmediateTransactions + end ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SQLite3Configuration end From 43bd67305d24a95e2432a7cf80e99a95cc0b8706 Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Tue, 6 Jan 2026 12:09:22 +0100 Subject: [PATCH 4/4] Try to make concurrency-on-conflict-discard tests less flaky --- test/integration/concurrency_controls_test.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/integration/concurrency_controls_test.rb b/test/integration/concurrency_controls_test.rb index d9e72b85..a12c48e4 100644 --- a/test/integration/concurrency_controls_test.rb +++ b/test/integration/concurrency_controls_test.rb @@ -197,6 +197,8 @@ class ConcurrencyControlsTest < ActiveSupport::TestCase test "discard jobs when concurrency limit is reached with on_conflict: :discard" do job1 = DiscardableUpdateResultJob.perform_later(@result, name: "1", pause: 3) + sleep(0.1) + # should be discarded due to concurrency limit job2 = DiscardableUpdateResultJob.perform_later(@result, name: "2") # should also be discarded @@ -219,8 +221,9 @@ class ConcurrencyControlsTest < ActiveSupport::TestCase test "discard on conflict across different concurrency keys" do another_result = JobResult.create!(queue_name: "default", status: "") - DiscardableUpdateResultJob.perform_later(@result, name: "1", pause: 0.2) - DiscardableUpdateResultJob.perform_later(another_result, name: "2", pause: 0.2) + DiscardableUpdateResultJob.perform_later(@result, name: "1", pause: 2) + DiscardableUpdateResultJob.perform_later(another_result, name: "2", pause: 2) + sleep(0.1) DiscardableUpdateResultJob.perform_later(@result, name: "3") # Should be discarded DiscardableUpdateResultJob.perform_later(another_result, name: "4") # Should be discarded