ExecutionContext.after_change を Ractor セーフに実装

rails/rails

ActiveSupport::ExecutionContext.after_change が保持するコールバック配列を不変化し、Ractor で安全に共有できるように変更されました。既存の API はそのまま動作し、フレームワーク全体の Ractor 対応が一歩前進します。

背景

ExecutionContext の after_change コールバックは単なる Proc の配列で、Ractor 間で共有できないという問題がありました。 Ractor はオブジェクトの共有可能性を厳格にチェックし、共有不可能な Proc が渡されると例外が発生します。現在の実装では @after_change_callbacks が可変の配列であり、各要素はそのままの Proc です。そのため、Ractor 環境でコールバックが呼び出されると安全性が保証されません。この隠れた非安全領域を解消することが本 PR の主目的です。

利用頻度は限定的でドキュメント化もされていませんが、内部的に ActiveRecord のクエリログクリアなどで使用されています。 小さな使用箇所でも Ractor で例外が起きるリスクは無視できません。従来は開発者が手動で shareable にする必要があり、統一的な対策が欠如していました。安全な実装に統一することで、将来的な拡張や他コンポーネントへの波及効果が期待できます。

技術的な変更

@after_change_callbacks の初期化と更新ロジックが不変・凍結された配列に置き換えられました。 activesupport/lib/active_support/execution_context.rb では初期化を [].freeze に変更し、after_change メソッドは [@*after_change_callbacks, block].freeze で新しい配列を作成して再代入します。

@@
-    @after_change_callbacks = []
+    @after_change_callbacks = [].freeze
@@
-        @after_change_callbacks << block
+        @after_change_callbacks = [*@after_change_callbacks, block].freeze

この変更により配列自体が凍結され、Ractor が要求する「共有可能なオブジェクト」条件を満たします。既存コードは after_change の呼び出し方を変更せずにそのまま利用できます。

コールバックを登録する側も Ractor 共有可能な Proc にラップする必要があります。 activerecord/lib/active_record/query_logs.rb では従来の after_change { … }ActiveSupport::Ractors.shareable_proc でラップした形に置き換えました。

@@
-      ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
+      ActiveSupport::ExecutionContext.after_change(&ActiveSupport::Ractors.shareable_proc { ActiveRecord::QueryLogs.clear_cache })

shareable_proc により生成される Proc は Ractor が安全に参照できることが保証され、実行時エラーが防止されます。このラップは明示的な意図表示でもあり、他のコンポーネントでも同様のパターンを採用しやすくなります。

テストコードが Ractor 共有可能性を検証するよう拡張されました。 activesupport/test/execution_context_test.rbactive_support/testing/ractors_assertions の require と include ActiveSupport::Testing::RactorsAssertions が追加され、新たに「callbacks are ractor safe」テストが実装されています。

+require "active_support/testing/ractors_assertions"
@@
   include ActiveSupport::ExecutionContext::TestHelper
+  include ActiveSupport::Testing::RactorsAssertions
@@
+  test "callbacks are ractor safe" do
+    ActiveSupport::ExecutionContext.with(after_change_callbacks: [].freeze) do
+      ActiveSupport::ExecutionContext.after_change(&ActiveSupport::Ractors.shareable_proc { })
+
+      assert_ractor_shareable ActiveSupport::ExecutionContext.after_change_callbacks
+    end
+  end

追加されたテストは assert_ractor_shareable を用いて、after_change_callbacks が凍結された配列かつ Ractor で共有可能であることを自動的に検証します。テストがパスすれば、実装が期待通り Ractor セーフであることが保証されます。

設計判断

配列を凍結し、新しい配列で置き換えるイミュータブル操作は、既存 API 互換性を保つ最小侵害の選択です。 以前は << で直接配列を変更していたため、Ractor では不変性が欠如していました。凍結された配列に対しては << が禁じられるため、誤ったミューテーションを防止しつつ、after_change のシグネチャは変更せずに利用できます。結果として、既存コードは修正不要で安全性だけが向上します。

ActiveSupport::Ractors.shareable_proc の導入は、開発者に意図的な共有を明示させる設計です。 ラップ無しの Proc が渡された場合は Ractor で例外になるため、明示的にラップすることで意図がコード上に残ります。これにより将来的なバグを防ぎ、フレームワーク全体で共有可能なコールバック作成のベストプラクティスが確立されます。他コンポーネントが同様のパターンを踏襲しやすくなる副次的効果があります。

まとめ

本 PR は ExecutionContext.after_change の内部データ構造を不変・凍結に改め、 そして shareable_proc でラップしたコールバックを登録 することで、Ractor 環境でも例外なく動作できるようにしました。API 互換性は維持され、追加テストで安全性が検証されたため、Rails の Ractor 対応が着実に前進します。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
a4767ac1

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的な変更、設計判断、まとめの5要素が明確に配置されており、総論→各論→結論の流れが保たれています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトが正しい形式で使用され、PRリンクも # を含むテキストで正しくリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

対象はRailsの内部実装に詳しいエンジニアであり、初心者向けの余計な説明はなく適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成になり、トピックセンテンスが先頭に置かれ、段落は適切な長さで空行で区切られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供されたDiffと完全に一致し、ファイル名・変更内容も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Ractor、shareable_proc、凍結、イミュータブルなどの用語がPRと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な主張はDiffとPRの記述に基づき正確で、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての事実はPRタイトル・説明・Diffで裏付けられており、外部知識や根拠のない推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号やファイルパスなどの固有名詞は正しく記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRタイトルの意味を日本語で正確に表現しており、一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事はPR情報以外のバージョンサポートやリリース日程などの外部知識を含んでいません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現に関する記述はなく、PR情報と食い違いもありません。