`ActiveSupport::EventReport::LogSubscriber` の `event_log_level` を Ractor セーフに
この PR は、ActiveSupport::EventReport::LogSubscriber の event_log_level API が Ractor 間で安全に共有できるよう、内部ハッシュの構築と凍結方法を変更します。既存の挙動を保ちつつ、共有可能な不変オブジェクトにすることで Ractor 環境での使用を可能にします。
背景
Ractor 安全性 が求められる場面では、ミュータブルなオブジェクトを複数の Ractor で共有すると予期しない競合が起こります。LogSubscriber の event_log_level は以前、log_levels[method_name.to_s] = level と直接ハッシュを変更していたため、同一ハッシュが複数 Ractor に渡された際に安全性が確保できませんでした。PR の目的は、このハッシュを不変(freeze)かつコピーされた形で扱うようにし、Ractor でも問題なく利用できるようにすることです。
技術的な変更
log_levels の更新ロジック が immutable なパターンに置き換えられました。具体的には、ハッシュをその場で上書きする代わりに merge で新しいハッシュを生成し、freeze してから self.log_levels に代入します。
@@
- log_levels[method_name.to_s] = level
+ self.log_levels = log_levels.merge(method_name.to_s => level).freeze
さらに、クラス属性の初期値も凍結された空ハッシュに変更され、属性そのものが不変となります。
- class_attribute :log_levels, default: {} # :nodoc:
+ class_attribute :log_levels, default: {}.freeze # :nodoc:
テスト も追加され、log_levels が Ractor で共有可能(shareable)であることを assert_ractor_shareable で検証しています。
+require "active_support/testing/ractors_assertions"
+
+class ActiveSupport::EventReporter::LogSubscriberTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::RactorsAssertions
@@
+ test "MyLogSubscriber.event_log_level is ractor safe" do
+ assert_ractor_shareable MyLogSubscriber.log_levels
+ end
設計判断
既存 API の維持 を最優先に、実装だけを不変オブジェクトに置き換える方針が取られました。event_log_level の呼び出し側は変更不要で、log_levels が自動的に新しい凍結ハッシュへ置き換わります。これにより、従来の log_levels[method] = level の副作用は排除され、Ractor 環境でも安全に利用できます。
class_attribute のデフォルトを凍結 した選択は、サブクラスが継承したときに同じハッシュインスタンスを共有しないようにするための防御策でもあります。凍結されたハッシュは変更できないため、予期しないミューテーションが発生しません。コード変更は最小限に抑えられ、動作上の後方互換性は維持されています。
まとめ
本変更により、ActiveSupport::EventReport::LogSubscriber の event_log_level が Ractor 安全な不変ハッシュを使用するようになり、共有可能性が確保されました。既存の挙動は変わらず、Ractor を活用したアプリケーションでもログレベルの設定が安全に行えるようになります。