Messages::Metadata のシリアライザ配列をデフォルトで凍結
Rails のグローバル状態を Ractor セーフにする取り組みの一環で、Messages::Metadata が保持するシリアライザ配列をデフォルトで凍結し、共有可能にしました。これにより、既存の振る舞いを変えずにスレッド間で安全に利用できるようになります。
背景
Messages::Metadata ではメッセージのエンベロープとタイムスタンプをシリアライズするために ENVELOPE_SERIALIZERS と TIMESTAMP_SERIALIZERS という定数配列が使用されてきました。これらの配列はロードフック内で要素を追加し、その後 freeze されていたため、ロード時にミューテーションが発生し Ractor 共有が不可能でした。したがって、定数をそのまま残しつつ安全に共有できる形にリファクタリングする必要がありました。
技術的な変更
singleton_class.attr_accessor を用いて use_message_serializer_for_metadata に加え envelope_serializers と timestamp_serializers をモジュール属性として定義し、初期値として配列を設定してから freeze しています。これにより、属性は読み取り専用の frozen 配列を保持しつつ、ロードフック内で安全に再割り当てできるようになりました。
変更前:
module ActiveSupport
module Messages # :nodoc:
module Metadata # :nodoc:
ENVELOPE_SERIALIZERS = [
*SerializerWithFallback::SERIALIZERS.values,
ActiveSupport::JSON,
::JSON,
Marshal,
]
TIMESTAMP_SERIALIZERS = [
SerializerWithFallback::SERIALIZERS.fetch(:message_pack),
SerializerWithFallback::SERIALIZERS.fetch(:message_pack_allow_marshal),
]
変更後:
module ActiveSupport
module Messages # :nodoc:
module Metadata # :nodoc:
singleton_class.attr_accessor :use_message_serializer_for_metadata, :envelope_serializers, :timestamp_serializers
self.envelope_serializers = [
*SerializerWithFallback::SERIALIZERS.values,
ActiveSupport::JSON,
::JSON,
Marshal,
].freeze
self.timestamp_serializers = [
SerializerWithFallback::SERIALIZERS.fetch(:message_pack),
SerializerWithFallback::SERIALIZERS.fetch(:message_pack_allow_marshal),
].freeze
ロードフックの更新:
ActiveSupport.on_load(:message_pack) do
ActiveSupport::Messages::Metadata.envelope_serializers = (ActiveSupport::Messages::Metadata.envelope_serializers | [ActiveSupport::MessagePack]).freeze
ActiveSupport::Messages::Metadata.timestamp_serializers = (ActiveSupport::Messages::Metadata.timestamp_serializers | [ActiveSupport::MessagePack]).freeze
end
use_message_serializer_for_metadata? と pick_expiry メソッドは、それぞれ Metadata.envelope_serializers と Metadata.timestamp_serializers を参照するよう変更され、呼び出しロジックは変わりません。これにより、配列自体は immutable でありながら、ロード時に安全に拡張できる設計となっています。
設計判断
定数からモジュール属性への移行 は、既存の公開 API を維持しつつ Ractor 共有可能な状態を実現するために選択されました。属性は attr_accessor により外部から再代入可能であるものの、ロードフック以外で変更されることは想定されておらず、実質的に不変オブジェクトとして扱われます。結果として、既存コードは Metadata::ENVELOPE_SERIALIZERS などを直接参照していた場合でも、同等の値を取得できるため互換性が保たれます。
まとめ
本変更は Messages::Metadata のシリアライザ配列をデフォルトで凍結し、モジュール属性として管理することで Ractor セーフな共有を実現しました。振る舞いは変わらず、将来的な拡張やメンテナンス時にも安全に扱える基盤が整備されたことが最大の意義です。