Rescue ハンドラをデフォルトで Ractor 安全にする変更
ActiveSupport::Rescuable の rescue_handlers がデフォルトで freeze され、追加時にも freeze された配列を再構築するようになり、Ractor 環境で安全に共有できるようになりました。
背景
Ractor へ移行する際、共有できないオブジェクトが原因で例外ハンドラが正しく機能しないリスクがありました。
rescue_handlers は内部で配列として保持され、ハンドラは Proc オブジェクトで呼び出されますが、Proc はデフォルトでは Ractor 共有不可です。
そのため、デフォルト状態で安全な形に変更することで、Ractor に依存しないコードでも安心して例外処理を利用できるようになります。
技術的な変更
Rescuable モジュールは rescue_handlers の初期化と追加ロジックに freeze を導入し、配列自体と要素を不変にしました。
変更前は class_attribute :rescue_handlers, default: [] と定義され、self.rescue_handlers += [[key, with]] で配列を拡張していました。
class_attribute :rescue_handlers, default: []
self.rescue_handlers += [[key, with]]
変更後はデフォルト配列を [].freeze とし、追加時には既存配列を展開し新要素を freeze した上で全体を再度 freeze しています。
class_attribute :rescue_handlers, default: [].freeze
self.rescue_handlers = [*rescue_handlers, [key, with].freeze].freeze
この実装により、rescue_handlers は Ractor で共有可能な不変オブジェクトとなり、テストでも assert_ractor_shareable が成功することが確認されています。
設計判断
不変オブジェクトを利用して共有可能性を保証する方針が採用され、既存コードへの影響を最小限に抑えています。
freeze を追加しただけで API 変更はなく、rescue_from の呼び出し側は従来通りシンボルやブロックを渡せます。Proc を Ractor 共有にしたい場合は、利用者が ActiveSupport::Ractors.shareable_proc を明示的に使用します。
この設計は後方互換性と安全性のバランスをとり、Ractor 環境での例外ハンドリングを自然に利用できる基盤を提供します。
まとめ
rescue_handlers をデフォルトで freeze し、追加時も不変配列として再構築することで、例外ハンドラが Ractor で安全に共有できるようになりました。既存の使用方法は変わらず、必要に応じて shareable_proc を利用すればプロシージャも安全に扱えます。