Active Record Railtie を Ractor 対応に
Active Record の Railtie が Ractor 環境でも安全に動作できるよう、メッセージ検証用プロックを共有可能に変更しました。これにより、従来の動作を保持しつつ、Ractor の並行実行時に起因するプロックのコピーエラーを防止します。
背景
Active Record の Railtie はアプリケーション起動時にメッセージ検証ロジックを構成しますが、Ractor が導入されるとプロックが複数の Ractor 間で共有できず例外が発生する可能性があります。従来は app.message_verifiers.prepend や rotate に直接ブロックを渡していたため、Ractor 安全性が確保されていませんでした。今回の変更は、その安全性を確保しつつ機能的な振る舞いを変えないことを目的としています。
技術的な変更
legacy_options の不変化
legacy_options ハッシュに対して .freeze が付与され、変更不可オブジェクトとして扱われるようになりました。これにより、複数の Ractor が同一ハッシュ参照を共有しても予期しないミューテーションが起きません。変更前後は以下の通りです。
- legacy_options = { digest: "SHA256", serializer: JSON, url_safe: true }
+ legacy_options = { digest: "SHA256", serializer: JSON, url_safe: true }.freeze
メッセージ検証プロックの共有化
app.message_verifiers に渡されるブロックが ActiveSupport::Ractors.shareable_proc でラップされ、Ractor 間で安全に共有できるよう変更されました。prepend と rotate の両方で同様のラップが適用され、ブロックのロジック自体は変更されていません。shareable_proc は Ruby の Ractor 機能に合わせたラッパーで、プロックをコピーせずに共有可能にします。
- app.message_verifiers.prepend { |salt| legacy_options if salt == "active_record/signed_id" }
+ app.message_verifiers.prepend(&ActiveSupport::Ractors.shareable_proc { |salt| legacy_options if salt == "active_record/signed_id" })
- app.message_verifiers.rotate { |salt| legacy_options if salt == "active_record/signed_id" }
+ app.message_verifiers.rotate(&ActiveSupport::Ractors.shareable_proc { |salt| legacy_options if salt == "active_record/signed_id" })
この変更により、Ractor 環境下でもメッセージ検証プロセスが例外なく実行されます。
設計判断
ActiveSupport::Ractors.shareable_proc を利用して既存のブロックを包む方針が採用され、API の公開や呼び出し側のコードを変更する必要がありませんでした。代替案として新しい設定キーやメソッドを導入する案も考えられましたが、後方互換性と最小侵襲を重視して既存ロジックのラップに留める判断が下されました。legacy_options を immutable にした点も同様に、データ共有時の安全性を高めるための設計的配慮です。
まとめ
この PR は、Active Record Railtie のメッセージ検証ロジックを Ractor 安全にするための最小限の変更です。legacy_options の freeze と shareable_proc によるブロック包装により、従来の振る舞いを維持しつつ Ractor 環境での例外を防止します。結果として、Rails アプリケーションは Ractor を活用した並行実行へシームレスに移行できるようになります。