ActiveSupport::JSON エンコーダを Ractor 共有可能に
ActiveSupport の JSON エンコーダが Ractor から安全に利用できるようになり、Ruby の並行実行モデルでのクラッシュリスクが排除されました。
背景
Ractor は Ruby の並行実行ユニットですが、オブジェクトが共有不可能な場合は Ractor.make_shareable が必要となり、既存の JSON エンコーダでクラッシュが報告されていました。PR #57814 で一時的な回避策が実装されたものの、根本的な対応が求められていました。本 PR はその根本修正として、エンコーダ自体を Ractor 共有可能 (shareable) にする変更を加えています。
技術的な変更
文字列リテラルと正規表現の shareable 化
activesupport/lib/active_support/json/encoding.rb では、HTML エスケープ用文字列リテラルを - 演算子でラップし、shareable な文字列に変換しました。
- U2028 => '\u2028'.b,
+ U2028 => -'\u2028'.b,
同様に HTML_ENTITIES_REGEX、FULL_ESCAPE_REGEX、JS_SEPARATORS_REGEX に対して .freeze を付与し、不変かつ共有可能 な正規表現オブジェクトにしています。
- HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029]))
+ HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029])).freeze
オプションハッシュの不変化
JSONGemEncoder のコンストラクタで受け取った options を dup.freeze(または空ハッシュを {}.freeze)にし、変更不可 な状態で保持するようにしました。
- @options = options || {}
+ @options = options.dup.freeze || {}.freeze
CODER の shareable proc ラップ
JSONGemCoderEncoder で使用している ::JSON::Coder のブロックを ActiveSupport::Ractors.shareable_proc でラップし、生成された CODER オブジェクト自体も .freeze しています。これにより、Ractor 間で安全にプロックが共有できます。
- CODER = ::JSON::Coder.new do |value, is_key|
+ CODER = ::JSON::Coder.new(&ActiveSupport::Ractors.shareable_proc { |value, is_key|
# ...省略...
- end
+ }).freeze
テストの追加と条件付き実行
activesupport/test/json/encoding_test.rb に Ractor 共有可能性 を検証するテストを Ruby 4.0 以上でのみ有効化する条件分岐を追加しました。テストは ActiveSupport::Testing::Isolation を組み合わせ、実際に Ractor 内で ActiveSupport::JSON.encode を呼び出して期待通りの JSON 文字列が生成されることを確認します。
+if RUBY_VERSION >= "4.0"
+ class JSONRactorShareabilityTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ # ...省略...
+ end
+end
Strict warnings の拡張
tools/strict_warnings.rb に Ractor が実験的 API であることに対する警告 を抑制対象として追加し、開発者が意図的に Ractor を利用した際に不要な警告が出ないようにしています。
+ # Ractors are experimental
+ /Ractor( API) is experimental/,
設計判断
shareable_proc ラップ と文字列リテラルへの unary - 演算子の使用は、既存コードに最小限の侵入で Ractor 互換性を実現する選択です。これにより、エンコーダ本体のロジックは変更せず、後方互換性 を保ちつつ共有可能オブジェクトへ変換できます。また、正規表現やオプションハッシュを .freeze することで、不変性 を保証し、Ractor 間でのデータ競合リスクを排除しました。テストは Ruby 4.0 以上でのみ有効化し、古い Ruby 環境への影響を回避すると同時に、将来のバージョンでの安全性を検証しています。全体として、実装コストと安全性のバランスを取った設計判断といえます。
まとめ
本変更により、ActiveSupport::JSON のエンコーダは Ractor から直接呼び出してもクラッシュしない ことが保証され、並行実行環境での JSON シリアライズが安全になりました。コードベースへの影響は限定的で、既存の動作を変えずに共有可能性を付与した点が最大の価値です。