Ractor 安全のための定数凍結拡張
リード文: Action Pack の複数定数を freeze し、Ractor 環境での共有可能性を高めました。可変オブジェクトが原因で生じる競合リスクを排除しつつ、既存の振る舞いはそのまま保持します。
背景
Ractor が導入されたことで、プロセス間でオブジェクトを安全に共有できるかが重要課題となりました。特に文字列やハッシュといった 可変オブジェクト を定数として保持していると、Ractor 間でのコピーが発生し予期せぬミューテーションが起こります。過去のコミットで多くのリテラル定数は freeze されましたが、リテラルでないオブジェクトや cattr_accessor 経由で設定された定数 が残っていました。本 PR はそれら抜け漏れを埋め、Ractor で安全に使用できるようにすることが目的です。
技術的な変更
ActionDispatch::Http::ParamBuilder.default の凍結
ParamBuilder のクラス変数 default は make_default(100) によって生成されたインスタンスです。変更前はそのまま代入され、ミュータブルな状態でした。変更後は self.default = make_default(100).freeze とし、インスタンス全体を凍結しました。これにより、リクエストパラメータ構築時に共有されるオブジェクトが不変となります。
HTTP_METHOD_LOOKUP の生成方法と凍結
元の実装では空ハッシュ HTTP_METHOD_LOOKUP = {} を作成し、HTTP_METHODS.each で逐次的にキーとシンボルへ変換した値を代入していました。可変ハッシュが残るリスクがあったため、今回 each.with_object({}) でハッシュを構築し、最後に .freeze しています。結果として、HTTP メソッド名とシンボルのマッピングが不変オブジェクトとして保持されます。
NullContentTypeHeader の凍結
ContentTypeHeader 構造体のインスタンス NullContentTypeHeader は ContentTypeHeader.new nil, nil で生成され、変更前はミュータブルでした。変更後は同呼び出しに .freeze を付与し、NullContentTypeHeader = ContentTypeHeader.new(nil, nil).freeze としています。これにより、ヘッダ判定ロジックが参照するデフォルトオブジェクトが安全に共有できます。
UriEncoder 関連定数の凍結
DEC2HEX 配列の各要素は文字列リテラルですが、force_encoding 後に .freeze が付与されていませんでした。修正では (ENCODE % i).force_encoding(US_ASCII).freeze とし、配列全体も .freeze しています。また、ENCODER = UriEncoder.new を ENCODER = UriEncoder.new.freeze に変更し、エンコーダインスタンス自体を不変化させました。これにより URI エンコード処理が Ractor で安全に呼び出せます。
設計判断
既存定数の型やインターフェースは変更せず、freeze の付与のみで安全性を向上させる方針が採用されました。特に HTTP_METHOD_LOOKUP の生成を each.with_object に置き換えた点は、ハッシュ構築と同時に不変化を保証できるシンプルな実装です。新たな設定キーやラッパーメソッドを追加しないことで、既存コードとの互換性を保持しつつ Ractor 用の堅牢化を実現しています。
まとめ
本 PR は Action Pack の定数群に対し freeze を付与することで、Ractor 環境での不変性を保証しました。コードロジックはそのままで、ミューテーションによる競合リスクが除去されます。今後、Ractor 対応を進める際の安全基盤として機能します。