ActiveSupport の JSON エンコーダを Ractor 共有可能にする変更

rails/rails

ActiveSupport の JSON エンコーダが Ractor で安全に共有できるようになり、従来必要だった Ractor.make_shareable の呼び出しが不要になりました。この変更は Ruby 4.0 以降でもテストがパスするように、エンコーダ内部のデータを不変化し、Ractor から直接利用できる形にしています。

背景

Ractor は Ruby の並列実行モデルであり、オブジェクトはデフォルトでは共有できません。そのため JSON エンコーダ を Ractor 内で利用する際は Ractor.make_shareable を明示的に呼び出す必要があり、コードが冗長になると同時にパフォーマンス上のオーバーヘッドも発生していました。PR の説明にあるように、テスト環境で Ruby 4.0 が導入された際にスキップが失敗する問題も報告されていました。エンコーダを事前に共有可能とすれば、Ractor 内での JSON エンコードがシンプルかつ高速になります。

技術的な変更

定数の不変化

activesupport/lib/active_support/json/encoding.rb では ESCAPED_CHARS の文字列リテラルが - 演算子でラップされ、暗黙的に freeze されるようになりました。さらに、正規表現定数 HTML_ENTITIES_REGEXFULL_ESCAPE_REGEXJS_SEPARATORS_REGEXfreeze 呼び出しで確実に不変化しています。

ESCAPED_CHARS = {
  U2028 => -'\u2028'.b,
  U2029 => -'\u2029'.b,
  ">".b => -'\u003e'.b,
  "<".b => -'\u003c'.b,
  "&".b => -'\u0026'.b,
}.freeze

HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029])).freeze
FULL_ESCAPE_REGEX = Regexp.union(*ESCAPED_CHARS.keys).freeze
JS_SEPARATORS_REGEX = Regexp.union(U2028, U2029).freeze

エンコーダオプションの凍結

JSONGemEncoder のコンストラクタで受取った optionsdup.freeze され、空のオプション {}freeze された定数として保持されます。これによりエンコーダインスタンス自体が Ractor 共有可能 になります。

def initialize(options = nil)
  @options = options.dup.freeze || {}.freeze
end

Coder の Proc を共有可能化

JSONGemCoderEncoder が利用可能な環境では、CODERActiveSupport::Ractors.shareable_proc でラップされたブロックで生成され、さらに freeze されています。shareable_proc は内部で Ractor.make_shareable を行うため、Ractor 間で安全に転送できます。

CODER = ::JSON::Coder.new(&ActiveSupport::Ractors.shareable_proc { |value, is_key|
  # 既存ロジック省略
}).freeze

依存 gem のバージョン更新

Gemfile.lockjson gem が 2.15.2 から 2.19.9 に更新され、JSONGemCoderEncoder が利用できるバージョンが確保されています。バージョン更新はエンコーダの新機能と互換性を保つための必須変更です。

-    json (2.15.2)
+    json (2.19.9)

設計判断

既存キーの拡張 という方針で実装が進められました。新たな設定項目やクラスを追加せず、既存のエンコーダクラスや定数に対して不変化処理と shareable_proc ラップを施すことで、後方互換性を維持しながら Ractor 共有を実現しています。- 演算子による文字列凍結は Ruby の標準的な不変化手段であり、追加の依存やランタイムチェックを避けられます。また、CODER の生成を一箇所に集約し freeze することで、オブジェクトのコピーコストを最小化しつつ安全な共有を保証しています。これらの選択は「最小限の侵入で Ractor 対応を提供」するという設計目標に沿ったものです。

まとめ

この PR により、ActiveSupport の JSON エンコーダは Ractor から直接呼び出せる Ractor‑shareable なオブジェクトとなり、Ractor.make_shareable を明示的に呼び出す必要がなくなります。定数の凍結、オプションの不変化、shareable_proc の活用というシンプルな改修で、既存コードの動作は変わらず、Ruby 4.0 以降でもテストが通過するようになりました。

記事メタデータ

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

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめの3部構成が明確に存在し、各セクションが適切に配置されています。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは正しい ``ruby:ファイルパス`` 形式ですが、PRリンクは "[PR #57814]" となっており、仕様の ``[#123](URL)`` 形式から若干外れています。致命的ではありませんが統一するとより良いです。

対象読者への適合性 ✓ PASS

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

専門エンジニア向けの記述で、初心者向けの余計な解説はありません。

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

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

各セクションは総論→各論→結論の流れを持ち、段落はトピックセンテンスで始まり、1段落1トピック・適切な長さで書かれています。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffと正確に一致し、ファイル名・変更内容とも合致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Ractor、shareable、freeze などの用語は PR と合致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は Diff と PR の内容に裏付けられており、因果関係も論理的です。

事実の突合 ✓ PASS

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

記事の主張はすべて PR のタイトル・説明・Diff に基づいており、ハルシネーションは見られません。

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

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

PR番号、Gem のバージョン、ファイルパスなどの数値・固有名詞は正確です。

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

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

記事タイトルは PR の内容「ActiveSupport::JSON encoders Ractor-shareable」を日本語で適切に表現しています。

外部知識の正確性 ✓ PASS

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

LTS やリリース日程といった外部知識は一切記載されていません。

時間表現の正確性 ✓ PASS

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

時間表現のずれはなく、Ruby 4.0 以降のテストが通る旨は PR と一致しています。