LogSubscriber の log_levels を Ractor セーフにする変更
LogSubscriber のクラス属性 log_levels を凍結し、更新時にも凍結したハッシュを保持することで、Ractor 間で安全に共有できるようにしました。
背景
これまで LogSubscriber.log_levels は可変ハッシュとして定義されており、Ractor で共有する際に不変性が保証されていませんでした。Ractor はオブジェクトの共有に厳格な制約を設けており、共有可能(shareable)であることが要求されます。そのため、subscribe_log_level がハッシュをマージした結果をそのまま保持すると、Ractor 間で渡したときに例外が発生するリスクがあります。PR #57919 はこのリスクを排除し、log_levels を Ractor 安全にすることを目的としています。
技術的な変更
log_levels の初期化を凍結するため、class_attribute :log_levels のデフォルト値を {} から {}.freeze に変更しました。また、subscribe_log_level でハッシュをマージした後に .freeze を付与し、更新後も不変オブジェクトとなるようにしました。
@@
- class_attribute :log_levels, instance_accessor: false, default: {} # :nodoc:
+ class_attribute :log_levels, instance_accessor: false, default: {}.freeze # :nodoc:
@@
- self.log_levels = log_levels.merge(method => LEVEL_CHECKS.fetch(level))
+ self.log_levels = log_levels.merge(method => LEVEL_CHECKS.fetch(level)).freeze
この変更により、log_levels は常に凍結されたハッシュで保持され、Ractor が要求する shareable な状態が保証されます。LEVEL_CHECKS は既に freeze されているため、追加のロジックは不要です。
テストコードも Ractor 共有可能性のアサーション を追加し、MyLogSubscriber.log_levels が shareable であることを検証しています。
@@
+require "active_support/testing/ractors_assertions"
@@
include ActiveSupport::LogSubscriber::TestHelper
+ include ActiveSupport::Testing::RactorsAssertions
@@
def test_log_levels_are_ractor_safe
assert_ractor_shareable MyLogSubscriber.log_levels
end
設計判断
クラス属性の不変化を保証するという方針が採られました。既存の API(LogSubscriber.subscribe_log_level の呼び出し方)やデフォルト動作は変更せず、内部でハッシュを凍結するだけの最小限の侵入で実装されています。log_levels を別の属性名やラッパーメソッドでラップする案は検討されなかったようです。これにより、既存コードへの影響がなく、Ractor 対応という新たな要件だけを満たす設計となっています。
まとめ
LogSubscriber.log_levels を {}.freeze で初期化し、マージ後も .freeze して保持することで、Ractor 間で安全に共有できる不変ハッシュにしました。追加されたテストにより、その shareable 性が検証されています。この変更は振る舞いを変えずに Ractor 対応を実現し、今後の並行処理実装における安全性を高めます。