ActiveSupport::JSON エンコーダを Ractor 共有可能に

rails/rails

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_REGEXFULL_ESCAPE_REGEXJS_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 のコンストラクタで受け取った optionsdup.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.rbRactor 共有可能性 を検証するテストを 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.rbRactor が実験的 API であることに対する警告 を抑制対象として追加し、開発者が意図的に Ractor を利用した際に不要な警告が出ないようにしています。

+    # Ractors are experimental
+    /Ractor( API) is experimental/,

設計判断

shareable_proc ラップ と文字列リテラルへの unary - 演算子の使用は、既存コードに最小限の侵入で Ractor 互換性を実現する選択です。これにより、エンコーダ本体のロジックは変更せず、後方互換性 を保ちつつ共有可能オブジェクトへ変換できます。また、正規表現やオプションハッシュを .freeze することで、不変性 を保証し、Ractor 間でのデータ競合リスクを排除しました。テストは Ruby 4.0 以上でのみ有効化し、古い Ruby 環境への影響を回避すると同時に、将来のバージョンでの安全性を検証しています。全体として、実装コストと安全性のバランスを取った設計判断といえます。

まとめ

本変更により、ActiveSupport::JSON のエンコーダは Ractor から直接呼び出してもクラッシュしない ことが保証され、並行実行環境での JSON シリアライズが安全になりました。コードベースへの影響は限定的で、既存の動作を変えずに共有可能性を付与した点が最大の価値です。

記事メタデータ

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

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的変更・設計判断・まとめが順序立てて配置されており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトは正しい形式 ```ruby:path です。GitHubリンクもPR番号が #57842 でリンク化されており、要件を満たしています。

対象読者への適合性 ✓ PASS

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

対象はRailsエンジニアで、専門用語や実装詳細に焦点が当てられており、初心者向けの過剰な説明はありません。

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

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

各セクションは総論パラグラフ→具体例→まとめの流れになっており、段落は1文で要点が示され、長さも適切です。

Diff内容との照合 ⚠ WARNING

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

ほとんどのコード変更はDiffと一致していますが、`FULL_ESCAPE_REGEX` と `JS_SEPARATORS_REGEX` の .freeze 追加が記事に省略されています。理解に支障はないものの、完全な照合ではありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「shareable」「freeze」「Ractor」などの用語は正しく使用され、PR文言と一致しています。

説明の技術的正確性 ✓ PASS

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

技術的な説明はDiffの内容と合致しており、誤った主張は見受けられません。

事実の突合 ✓ PASS

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

記事の全ての主張はPRのタイトル・説明・Diffで裏付けられており、推測や捏造はありません。

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

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

PR番号 #57842、ファイルパス、コード行の変更など数値・固有名詞は正確です。

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

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

記事タイトルはPRの内容「Make ActiveSupport::JSON encoders Ractor‑shareable」を正しく反映しています。

外部知識の正確性 ✓ PASS

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

記事はPRに記載された情報以外の外部知識(LTS、リリース日程等)を含んでいません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、Ruby 4.0 以上でテストが有効になる旨はDiffと一致しています。