`ActionDispatch::ExceptionWrapper` をRactor対応に変更し、設定APIを追加

rails/rails

ActionDispatch::ExceptionWrapper のクラス変数をすべてfreezeし、設定変更を新しい凍結オブジェクトへの再代入で行うことでRactor安全にしました。あわせて wrapper_exceptionssilent_exceptions の公開設定APIが追加されています。

背景

Ractor では、可変な共有状態(mutable shared state)を複数のRactorから操作することが禁止されています。変更前の ActionDispatch::ExceptionWrapper は、rescue_responsesrescue_templates などのクラス変数としてHashやArrayを可変のまま保持しており、初期化フェーズで merge! などの破壊的メソッドで書き換える設計でした。この構造は、将来Ractorを利用するアプリケーションにとって安全でない共有状態を持つことになるため、Ractor対応が必要でした。

rescue_responsesrescue_templates はエンジン側の設定(config.action_dispatch.rescue_responses / config.action_dispatch.rescue_templates)として既に公開されていましたが、wrapper_exceptionssilent_exceptions は公開設定が存在しませんでした。今回のPRはRactor安全化と同時に、この2つの設定APIを追加しています。

技術的な変更

exception_wrapper.rb での変更はシンプルで、4つのクラス変数のデフォルト値すべてに .freeze を追加することで、オブジェクト自体を不変にしています。

変更前:

cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
  ...
)

cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
  ...
)

cattr_accessor :wrapper_exceptions, default: [
  "ActionView::Template::Error"
]

cattr_accessor :silent_exceptions, default: [
  "ActionController::RoutingError",
  "ActionDispatch::Http::MimeNegotiation::InvalidType"
]

変更後:

cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
  ...
).freeze

cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
  ...
).freeze

cattr_accessor :wrapper_exceptions, default: [
  "ActionView::Template::Error"
].freeze

cattr_accessor :silent_exceptions, default: [
  "ActionController::RoutingError",
  "ActionDispatch::Http::MimeNegotiation::InvalidType"
].freeze

デフォルト値をfreezeしたことにより、railtie.rb でのアプリケーション設定の適用方法も変わっています。破壊的な merge! による書き換えから、新しい凍結オブジェクトへの再代入に切り替えています。

変更前:

ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)

変更後:

ActionDispatch::ExceptionWrapper.rescue_responses = ActionDispatch::ExceptionWrapper.rescue_responses.merge(config.action_dispatch.rescue_responses).freeze
ActionDispatch::ExceptionWrapper.rescue_templates = ActionDispatch::ExceptionWrapper.rescue_templates.merge(config.action_dispatch.rescue_templates).freeze
ActionDispatch::ExceptionWrapper.wrapper_exceptions = (ActionDispatch::ExceptionWrapper.wrapper_exceptions | config.action_dispatch.wrapper_exceptions).freeze
ActionDispatch::ExceptionWrapper.silent_exceptions = (ActionDispatch::ExceptionWrapper.silent_exceptions | config.action_dispatch.silent_exceptions).freeze

Hashには非破壊的な merge(戻り値を使う)、Arrayには集合の和を求める | 演算子を使い、いずれも結果を freeze してクラス変数へ再代入しています。この初期化フェーズでの一度限りの再代入により、アプリケーション起動後はすべてのクラス変数が不変オブジェクトとなります。

また railtie.rb には、新しく追加された2つの設定のデフォルト値(空のコレクション)も追加されています。

config.action_dispatch.wrapper_exceptions = []
config.action_dispatch.silent_exceptions = []

設計判断

既存の設定キーを再代入で更新する方式 が採用されました。オブジェクトを直接変更する代わりに、新しいオブジェクトを生成して代入することでRactor安全性を確保しています。この設計では、初期化タイミング(initializer ブロック)が一度しか実行されないことが前提であり、起動完了後はクラス変数が変化しないことが保証されます。

アプリケーションから設定を追加する際は、+=|= ではなく設定ファイルを通じた公式APIを利用することが想定されています。wrapper_exceptionssilent_exceptions については config.action_dispatch.wrapper_exceptions += [MyException] のように既存のデフォルト値を保持しながら追加できます(| 演算子による結合のため、重複は排除されます)。

なお、rescue_responses の場合は Hash#merge の結合方式(後者優先)、wrapper_exceptions / silent_exceptions の場合は Array#| の集合和で結合方式が異なります。これはそれぞれのデータ構造の特性を活かした適切な選択です。

まとめ

この変更は、デフォルトオブジェクトのfreeze化と設定適用時の再代入方式への切り替えという最小限の変更で、ActionDispatch::ExceptionWrapper をRactor対応にしています。同時に wrapper_exceptionssilent_exceptions の設定APIが公開されたことで、フレームワークやプラグインレベルの例外に対するバックトレース表示のカスタマイズが、従来の内部API操作ではなく公式の設定体系で行えるようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
55584060

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術詳細(各論)→まとめ(結論)の3部構成が明確に守られています。設計判断セクションも含まれており、記事の深みを増しています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:path```)とPR番号のリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

「Ractor」「mutable shared state」「railtie」などの用語を前提として話が進んでおり、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクション、各パラグラフがトピックセンテンスから始まる構成になっており、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているすべてのコードブロックは、提供されたDiff情報と正確に一致しています。ファイルパスの指定も正しいです。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Ractor安全」「破壊的メソッド」「非破壊的メソッド」「再代入」など、文脈に応じた技術用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

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

`.freeze`の追加と再代入方式への変更がRactor安全性を確保する理由について、技術的に正確かつ分かりやすく説明されています。

事実の突合 ✓ PASS

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

記事の内容はすべてPRのTitle, Description, Diffから裏付けが取れます。「設計判断」セクションはPRに明記されていませんが、コードの意図を解説する妥当な推論であり、ハルシネーションには該当しません。

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

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

PR番号(#57483)、クラス名、設定キーなどの固有名詞はすべて正確です。

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

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

記事のタイトルは「Ractor対応」と「設定APIの追加」というPRの2つの主要な変更点を的確に要約しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

記事には、PR情報に基づかないバージョンサポート情報やリリース日程などの外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

既存の設定と新規追加の設定に関する記述(「既に公開されていました」「公開設定が存在しませんでした」)が、PR Descriptionの内容と一致しており、時間的な前後関係が正確です。