Normalization.normalizes を Ractor セーフに
ActiveModel の属性正規化で使用される normalized_attributes が Ractor 間で安全に共有できるようになり、振る舞いを変えずにマルチプロセス実行環境への適合性が向上します。
背景
属性正規化は Normalization モジュールの normalized_attributes に属性名を保持し、バリデーション時に参照されます。 Ractor ではオブジェクトが shareable である必要があり、ミュータブルな Set は共有できません。そのため、Ractor 環境で正規化属性を使用すると Ractor::IsolationError が発生する可能性がありました。PR はこのリスクを排除し、既存の API を保ちつつ安全性を確保することを目的としています。
技術的な変更
class_attribute :normalized_attributes のデフォルト値を凍結した Set に変更しました。 初期化時点で不変オブジェクトとなり、Ractor が直接参照できるようになります。
- class_attribute :normalized_attributes, default: Set.new
+ class_attribute :normalized_attributes, default: Set.new.freeze
normalizes メソッド内で属性名を追加するロジックを変更し、コピー後に凍結した新しい Set を代入します。 これにより、既存の集合はミュータブルなまま残らず、常に凍結されたインスタンスが保持されます。
- self.normalized_attributes += names.map(&:to_sym)
+ self.normalized_attributes = (normalized_attributes + names.map(&:to_sym)).freeze
テストスイートに Ractor 安全性を検証するケースを追加しました。 ActiveSupport::Testing::RactorsAssertions をインクルードし、assert_ractor_shareable マクロで normalized_attributes が共有可能であることを確認します。
@@ -1,8 +1,11 @@
# frozen_string_literal: true
require "cases/helper"
+require "active_support/testing/ractors_assertions"
class NormalizedAttributeTest < ActiveModel::TestCase
+ include ActiveSupport::Testing::RactorsAssertions
@@
test ".normalized_attributes is ractor safe" do
assert_ractor_shareable NormalizedAircraft.normalized_attributes
end
end
設計判断
freeze を利用して不変オブジェクトを作成し、Ractor での共有を実現しています。 変更は normalized_attributes の生成と更新時にのみ行われ、既存コードからの呼び出し方法や戻り値の型は変わりません。ミュータブルな集合を直接保持しないことで、Ractor の安全性要件を満たす設計となっています。
属性集合の更新は新しいインスタンスを生成して代入する方式を採用しています。 これにより、読み取り側は常に凍結されたオブジェクトを取得し、書き込み側だけが新しい集合を作成します。結果として、振る舞いの変更はなく、Ractor 環境での例外が防止されます。
まとめ
Normalization.normalizes の実装が Ractor セーフとなり、normalized_attributes が不変かつ共有可能になることで、マルチプロセス環境でも属性正規化が安全に動作します。既存 API と挙動は維持されるため、利用者への影響はありません。