ActionController::Flash.add_flash_types を Ractor 安全化
ActionController のフラッシュタイプ登録メソッドが、Ractor 環境で安全に共有できるよう実装が変更されました。動作は従来と同等ですが、内部で不変オブジェクトを扱うことでマルチスレッド・マルチプロセス間のデータ競合を防止します。
背景
Ractor が導入された Rails では、オブジェクトが 共有可能(shareable) であることが求められます。ActionController::Flash._flash_types はクラス属性として配列を保持しており、従来はミュータブルなままで .freeze が適用されていませんでした。そのため、Ractor 間で参照された際に不変保証が破られ、例外が発生するリスクがありました。
このリスクを解消するために、フラッシュタイプの管理を不変オブジェクトベースに置き換える変更が実装されました。
技術的な変更
actionpack/lib/action_controller/metal/flash.rb では、クラス属性の初期化と追加ロジックが以下のように変更されました。
変更前:
class_attribute :_flash_types, instance_accessor: false, default: []
変更後:
class_attribute :_flash_types, instance_accessor: false, default: [].freeze
初期値を .freeze したことで、デフォルト配列自体が不変になります。さらに add_flash_types メソッドの内部処理も次のように変更されました。
変更前:
self._flash_types += [type]
変更後:
self._flash_types = (_flash_types | [type]).freeze
配列を直接破壊的に変更する代わりに、集合演算 (|) で重複を除去しつつ新しい要素を加え、結果を即座に .freeze しています。このパターンは Ractor での共有時に安全な不変オブジェクトを生成する公式ガイドラインに沿っています。
テストコードも actionpack/test/controller/flash_test.rb に Ractor 共有性を検証するアサーションを追加し、ActiveSupport::Testing::RactorsAssertions をインクルードしています。
require "active_support/testing/ractors_assertions"
...
include ActiveSupport::Testing::RactorsAssertions
...
def test_flash_types_are_ractor_safe
assert_ractor_shareable TestController._flash_types
end
このテストは TestController._flash_types が Ractor に対して shareable であることを自動的に検証し、変更が意図した安全性を満たすことを保証します。
設計判断
今回の実装では、既存の class_attribute キー _flash_types を拡張 する方針が選択されました。代替案としては新たな設定キーや別のデータ構造を導入することも考えられましたが、後方互換性 と 最小限の差分 を保つために、同一属性を不変化させるだけに留めました。`
self._flash_types = (_flash_types | [type]).freeze という一行の変更で、既存コードが期待する配列操作の結果をそのまま提供しつつ、Ractor 環境で安全に共有できるようになります。ミュータブルな配列を保持し続けるリスクと、コードベースへの侵入度を比較した結果、不変オブジェクト化 が最も合理的なトレードオフと判断されました。
まとめ
ActionController::Flash.add_flash_types は、内部で使用する配列を 凍結された不変オブジェクト に置き換えることで Ractor 共有可能となり、マルチスレッド・マルチプロセス環境での安全性が向上しました。動作上の変更はなく、既存のフラッシュタイプ機能はそのまま利用できるため、開発者への影響は最小限です。