DiffDaily

Deep & Concise - OSS変更の定点観測

[rails/solid_queue] Rails 8.2の`enqueue_after_transaction_commit`再導入に伴うテスト適応

rails/solid_queue

背景

Rails 8.2でenqueue_after_transaction_commit設定が再導入されました(rails/rails#55788)。この設定はRailsバージョンを跨いで異なる挙動を示してきたため、Solid Queueのテストコードをこれらの変遷に対応させる必要がありました。

設定の変遷:
- Rails 7.1: メソッド自体が存在しない(遅延エンキューなし)
- Rails 7.2: :default(アダプターのメソッドに委譲、Solid Queueではtrueを返す)
- Rails 8.0-8.1: false(遅延エンキューなし、設定は非推奨化)
- Rails 8.2: true(遅延エンキューがデフォルトで有効)

この変更により、トランザクション内でのジョブエンキューの挙動が変わるため、テストの条件分岐を適切に調整する必要があります。

主な変更内容

1. テストマトリクスの拡張

Rails mainブランチ(将来の8.2)に対するテストカバレッジを強化するため、Ruby 3.2の組み合わせを追加しました。

- ruby-version: "3.1"
  gemfile: rails_main
- ruby-version: "3.2"
  gemfile: rails_main

2. SQLite3の即時トランザクション処理の整理

Rails 8.0以降では即時トランザクションが標準サポートされたため、バージョンチェックを追加してモンキーパッチを条件付きで適用するように変更しました。

module SqliteImmediateTransactions
  def begin_db_transaction
    log("begin immediate transaction", "TRANSACTION") do
      with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
        conn.transaction(:immediate)
      end
    end
  end
end

ActiveSupport.on_load :active_record do
  if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
    # Rails 8.0以降は即時トランザクションが組み込み済み
    if Rails::VERSION::MAJOR < 8
      ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SqliteImmediateTransactions
    end
    ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend SQLite3Configuration
  end
end

3. トランザクションテストのスキップ条件の改善

従来はRails 7.2のみをハードコードでスキップしていましたが、enqueue_after_transaction_commitの実際の設定値をチェックする汎用的な条件に変更しました。

変更前:

# Rails 7.2のみをスキップ
skip if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 2

変更後:

# enqueue_after_transaction_commitが有効な場合はスキップ
skip if ActiveJob::Base.respond_to?(:enqueue_after_transaction_commit) &&
        [ true, :default ].include?(ActiveJob::Base.enqueue_after_transaction_commit)

この変更により、Rails 7.2と8.2の両方で適切にテストがスキップされます。:defaultは実質的にtrueとして機能するため、両方の値をチェックしています。

4. 競合制御テストのタイミング調整

遅延エンキューが有効な場合、ジョブのエンキュータイミングが変わるため、テスト内にsleep(0.1)を追加してジョブが確実にエンキューされるまで待機するように修正しました。

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)  # ジョブのエンキューを待機

  # 同時実行数制限により破棄されるべき
  job2 = DiscardableUpdateResultJob.perform_later(@result, name: "2")
  job3 = DiscardableUpdateResultJob.perform_later(@result, name: "3")
  # ...
end

技術的な影響

enqueue_after_transaction_commitが有効な場合、ジョブはトランザクションコミット後まで実際のエンキューが遅延されます。これにより:

  1. トランザクションのロールバック: ロールバック時にジョブがエンキューされない保証が得られる
  2. タイミングの変化: テストコード内でのジョブの可視性が変わるため、適切な待機処理が必要
  3. 競合制御: 同時実行数制限などの機能が期待通りに動作するためには、ジョブが実際にエンキューされるタイミングを考慮する必要がある

この変更により、Solid QueueはRails 8.2の新しい動作に対応しつつ、過去のバージョンとの互換性も維持できるようになりました。